0

I've been trying to implement this answer into my app (Rails - passing parameters in link_to) As it addresses my problem directly but i can't get it to work. Can someone look at my code and tell my what i'm doing wrong. Here are my routes:

Rails.application.routes.draw do
  devise_for :admins, path: 'admins'
  devise_for :employees, path: 'employees'
  namespace :employees do
    resources :kudos
    resources :rewards, only: [:index, :show], shallow: true do
      resources :orders, only: [:create, :new]
    end
  end
  namespace :admins do
    resources :kudos, only: [:index, :destroy]
    resources :employees, only: [:index, :edit, :update, :destroy]
    resources :company_values
    resources :rewards
  end
  get '/admin' => "admins/pages#dashboard", :as => :admin_root
  root 'employees/kudos#index'
end

Here is my orders controller

module Employees
  module Rewards
    class OrdersController < EmployeesController
      def new
        @order= Order.new
       if params[:reward_id]
            @order.reward_id = params[:reward_id]
        end
      end
      def create
        @order = Order.new(order_params)
        @order.employee = current_employee
        @order.save
        flash[:notice] = 'Reward was successfully bought'
        redirect_to rewards_path
      end

        private

      def order_params
        params.require(:order).permit(:employee_id, :reward_id)
      end
    end
  end
end

and my rewards controller

class RewardsController < EmployeesController
    def index
     @rewards = Reward.all 
    end

    def show
      render :show, locals: { reward: Reward.find(params[:id]) }
    end

    private

    def reward_params
      params.require(:reward).permit(:title, :description, :price)
    end
  end

And last but not least my rewards index view:

<h1>Rewards listing page</h1>
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Price</th>
      <th>Show Reward</th>
      <th>Buy Reward</th>
    </tr>
  </thead>

  <tbody>
    <% @rewards.each do |reward| %>
      <tr>
        <td><%= reward.title %></td>
          <td><%= reward.price %></td>
        <td><%=button_to 'Show',  employees_reward_path(reward), method: :get %></td>
        <td><%=link_to 'Buy',  new_employees_reward_order_path(:order=>{:reward_id => reward.id}) %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Right now i'm getting the following error

Showing /home/valar/Dokumenty/ERP_v1/app/views/employees/rewards/index.html.erb where line #18 raised:

No route matches {:action=>"new", :controller=>"employees/orders", :order=>{:reward_id=>6}}, missing required keys: [:reward_id]
Did you mean?  new_employees_reward_order_url

And if I pass an instance variable like this (like the answer suggested):

link_to 'Buy',  new_employees_reward_order_path(:order=>{:reward_id => @reward.id})

i get the undefined method `id' for nil:NilClass error Please help me figure out what I'm doing wrong.

EDIT I enclose a pic of my structurestructure

Wojtek
  • 21
  • 6
  • ```ActionController::UrlGenerationError in Employees::Rewards#index Showing /home/valar/Dokumenty/ERP_v1/app/views/employees/rewards/index.html.erb where line #18 raised: No route matches {:action=>"new", :controller=>"employees/orders", :reward_id=>nil}, missing required keys: [:reward_id] ``` is what I'm getting after implementing your advice @dbugger – Wojtek Mar 10 '23 at 13:14
  • should be `link_to 'Buy', new_employees_reward_order_path(reward)` because you are looping through rewards – dbugger Mar 10 '23 at 13:15
  • @dbugger now i get ```Routing Error uninitialized constant Employees::OrdersController candidate = constant.const_get(name) ^^^^^^^^^^ Did you mean? Employees::RewardsController raise MissingController.new(error.message, error.name) ^^^^^ ``` i think i have a nesting problem somewhere...but i checked my catalogue structure and it's ok. – Wojtek Mar 10 '23 at 15:05
  • that's pretty clear that your routing is not in agreement with your controller class structure. And that's a different question. – dbugger Mar 10 '23 at 15:11
  • @dbugger could you at least please tell me if I could nest modules as shown in orders_controller. and i also enclose a pic of my catalogue structure if you could take a quick look at it... – Wojtek Mar 10 '23 at 16:19

1 Answers1

1

There are a lot of problems with this. Lets start with the routes - if you want to route to the controller Employees::Rewards::OrdersController you need to configure it explicitly:

resources :orders, only: [:create, :new], module: :rewards

Nesting resources doesn't imply module nesting. And your controller should look like this:

class Reward < ApplicationRecord
  has_many :orders
end
module Employees
  module Rewards
    class OrdersController < EmployeesController
      before_action :set_reward

      # GET /employees/rewards/1/orders/new
      def new
        @order = @reward.orders.new
      end

      # POST /employees/rewards/1/orders 
      def create
        @order = @reward.orders.new(employee: current_employee)
        # Do not just assume that the record will always be saved 
        if @order.save
          flash[:notice] = 'Reward was successfully bought'
          redirect_to rewards_path
        else
          render :new
        end
      end

      private

      def set_reward
        # this should cause a 404 - Not Found Response if the reward cannot be found
        @reward = Reward.find(params[:reward_id])
      end
    end
  end
end

Note that you should be getting the parent resource id from the URL and the current user from the session. So you don't actually need strong parameters.

And when creating a nested resource make sure you actually find the parent resource first so that an error is raised early and rescued with 404 - Not Found response if an invalid parent id is used. Otherwise you'll either end up with NoMethodErrors, foreign key violations or a lack of referential integrity.

You can then link to the new action either by calling the path helper with an instance of reward:

<%= link_to 'Buy', new_employees_reward_order_path(reward) %>

But since you don't seem to be actually taking any user input you could just create a button that goes straight to the create action:

<%= button_to 'Buy', employees_reward_orders_path(reward), method: :post %>
max
  • 96,212
  • 14
  • 104
  • 165
  • Thank you very much for the time you took in crafting your answer. What if this line ``` @reward = Reward.find(params[:reward_id])``` can't find the reward and gives me the```ActiveRecord::RecordNotFound in Employees::Rewards::OrdersController#create Couldn't find Reward without an ID``` error ? – Wojtek Mar 11 '23 at 10:42
  • Rails rescues `ActiveRecord::RecordNotFound` by sending a 404 response and rendering an error page which is the correct response if you try to add a nested resource to something that doesn't exist. – max Mar 11 '23 at 10:45
  • But the reward exists... – Wojtek Mar 11 '23 at 10:47
  • Ah sorry. What `Couldn't find Reward without an ID` means is that `params[:reward_id]` is actually nil. Why this is happening is something that you'll have figure out with some basic debugging. Check the value of `reward` in the view where you're creating the link, the rendered html and check the parameters from the rails log. – max Mar 11 '23 at 10:52
  • As a debugging step you can explicitly pass the parameter instead ie. `new_employees_reward_order_path(reward_id: reward.id)` but this should not actually be needed. – max Mar 11 '23 at 10:56
  • I'm sorry max but it's all new to me. When you say check the parameters from the rails log you mean the development.log file? If yes then it says ```Completed 404 Not Found``` and upon inspecting the Buy element in the html view it doesn't have any value, – Wojtek Mar 11 '23 at 11:35
  • Oh and passing explicitly the params (reward_id: reward.id) gives me the ```undefined method `orders' for # – Wojtek Mar 11 '23 at 11:50
  • Yeah you need to define a `has_many :orders` assocation in your Reward model. – max Mar 11 '23 at 13:51
  • you were right max i thought that i made that association but it appeared i made a mistke in it. So finaly this syntax works ``` <%=button_to 'Buy', employees_orders_path(reward_id: reward.id), method: :post%>``` – Wojtek Mar 11 '23 at 19:20