ActiveRecord::RecordNotFound "can't find without id" - ruby-on-rails

I have a method that toggles a boolean, but cannot get it to work at the controller level due to an ActiveRecord not found error.
My items_controller.rb:
def remove
#item = Item.friendly.find(params[:id])
respond_to do |format|
if #item.toggle_approved
format.html { redirect_to root_path }
else
format.html { render :show }
end
end
end
When I set the instance variable like this - #item = Item.friendly.find(params[:id]) - I get an error that says Couldn't find Item without an ID. But when I set it like this - #item = Item.find(params[:id]) - I get an error that says Couldn't find Item with 'id'=. I'm passing the object as an argument to the path, so I'm not sure why this isn't working.
My view:
<%= link_to "Remove this item", item_remove_path(#item), class: 'button button-wide red-button', method: :patch %>
My routes.rb
resources :items do
patch '/remove', to: 'items#remove', via: :patch
end

What you are doing wrong:
You have extended the restful resource items, to include remove, and you are expecting to get the :id in params. params[:id] is nil in the request which you can verify by seeing the rails development log for this request.
How to fix this:
Way 1:
Change to patch '/remove', to: 'items#remove', on: :member. Adding a member route will expose the :id of resource in request, enabling your request to process.
Way 2:
Use your existing resource patch '/remove', to: 'items#remove', but use params[:item_id] instead of params[:id] in request.
Note: You DO NOT have to expose :id by patch '/remove/:id',

Change the routes as follow
resouces :items do
member do
patch :remove
end
end
It will create patch items/:id/remove then you dont need to change anything in controller.

You are accessing item's id in items_controller.rb so you have to define a route that accepting id also as #kiddorails mention above in a comment.
patch '/remove/:id', to: 'items#remove', as: :remove_item, via: :patch

Related

Rails call a controller user-defined method

I am working with rails I have a controller name books and has a user defined method in it .I need to call this method so that i can see the output on console.And I dont want to call this method in helpers.
def approve
#user=current_user.users.find params[:id]
puts '#{#usery}'
end
Also I Have a link
<%= link_to 'approve',users_path,data: { :confirm => 'Are you sure to delete the folder and all of its contents?'} %>
.When i click on this link I want to call the above method on it .
You'll just need to define a route and call it through that:
#config/routes.rb
resources :users do
get :approve, on: :member
end
<%= link_to "Approve", users_approve_path(#user) %>
As #Rich suggested that, you can achieve it by member. Please note that when you'll create a member route in member block
resources :users do
member do
get 'approve'
end
end
then you'll get the params[:id]. Like
def approve
#user = User.find params[:id]
puts '#{#user}'
end
and when create a member route using :on then you'll get params[:user_id]. Like
def approve
#user = User.find params[:user_id]
puts '#{#user}'
end
Path will be same in both cases that is
<%= link_to "Approve", users_approve_path(#user) %>
Source Rails - Adding More RESTful Actions
Happy coding !!!

How to update a profile attribute by link_to

I want to update a Profile model attribute by using link_to. The Profile model have a lang column, and I want to change to :en.
I could find out that I should use method: :put.
<%= link_to t('english'), profile_path(profile: {lang: :en}), method: :put %>
But it's ends up with error:
ActionController::UrlGenerationError in StaticPages#home
Showing /Users/ironsand/dev/phrasebook/app/views/layouts/_header.html.erb where line #21 raised:
No route matches {:action=>"update", :controller=>"profiles", :profile=>{:lang=>:en}} missing required keys: [:id]
I have this line in routes.rb to use the path:
resources :profiles, only: :update
How can I enable the function like this?
I found a similar question, but the case is a bit difference.
Edit
class ProfilesController < ApplicationController
def update
return redirect_to root_path unless current_user # If user is not logged in, redirect to /
if current_user.profile.update(profile_params) # Don't forget about validation for lang in Profile model
redirect_to root_path
else
redirect_to root_path
end
end
private
def profile_params
params.require(:profile).permit(:lang)
end
end
You need to identify the profile somehow, that's why it asks for id. But you can update profile without id, you just need to improve update method:
def update
return redirect_to root_path unless current_user # If user is not logged in, redirect to /
if current_user.profile.update(profile_params) # Don't forget about validation for lang in Profile model
redirect_to success_path
else
redirect_to error_path
end
end
private
def profile_params
params.require(:profile).permit(:lang)
end
For route, try this:
resources :profiles, only: [] do
collection do
put :update
end
end
or just:
put '/profiles' => 'profiles#update'
Since you're updating a specific profile, you need to supply something that lets your controller know what profile you're updating.
As you can see from the error message generated, your controller can't identify which profile it is that you're asking to be updated. You need to supply the id of the profile in order to update it.
One way this could be achieved with link_to is as follows:
link_to t('english'), profile_path(id: #profile.id, lang: :en), method: :put
lang would then be available in your update action in params[:lang].

Routing Error uninitialized constant

I am trying to learn RoR.
MY controller is
class SectionController < ApplicationController
def new
if request.post?
u=SectionMst.new( :section_name => params[:section_name])
u.save
redirect_to("/section")
else
render
end
end
def index
#sections = SectionMst.all
end
def destroy
u=SectionMst.destroy(params[:id])
u.save
redirect_to("/section")
end
def edit
#user = SectionMst.find(params[:id])
end
end
and index.html.erb is
<%= link_to "Edit", edit_section_path(section.id), method: :edit %>
rake routes is
section_new POST /section/new(.:format) section#new
POST /section/:id/edit(.:format) section/:id#edit
section_index GET /section(.:format) section#index
POST /section(.:format) section#create
new_section GET /section/new(.:format) section#new
edit_section GET /section/:id/edit(.:format) section#edit
section GET /section/:id(.:format) section#show
PUT /section/:id(.:format) section#update
DELETE /section/:id(.:format) section#destroy
routes.rb is
post "section/new"
post "section/:id/edit"
resources :section
i am getting the
Routing Error
uninitialized constant Section
if i delete the second line of routes.rb
then i get
Routing Error
No route matches [POST] "/section/3/edit"
not able to get why???
Get rid of the first and second lines in your routes.rb. They're redundant. The resources will create these lines automatically.
The resources :section should be written as resources :sections. Notice that it's plural.
In your index.html.erb, you shouldn't mention method: at all. It's automatically set, and :edit as method doesn't exist. Method refers to put or get or delete, but you normally don't have to mention it.
You do not need this lines in your routes.rb
post "section/new"
post "section/:id/edit"
Change the third line to:
resources :sections #plural
If you delete them, you can hit the edit view using
<%= link_to "Edit", edit_section_path(section.id), method: :edit %>
which will hit your app at section/3/edit with a GET request.
In your edit.html.erb, you can then have fields to capture edits and do a PUT to /section/3.
Note that RAILS uses HTTP verbs to define the CRUD operations. Ref here.
Check your controller's file name because it should be plural. It is supposed to match the class name. So, you should rename app/controllers/section_controller.rb to app/controllers/sections_controller.rb.

Why Don't my Parameters get Passed in Ruby on Rails?

So, I'm really new to Ruby on Rails and the whole thing still sounds like Chinese to me. Excuse me if this is a stupid question, but I'm trying to make a new button on my app that lets me copy things.
On my form I have:
%span.btn.btn-mini.btn-inverse= link_to copy_image, copy_campaign_signal_processor_item_path(#campaign, #processor, item) ,data: { toggle:"modal",target: "#myModal"}, remote: true
That works fine and creates the button for me. Then, in routes.rb, I have:
resources :signal_processors, exclude: [:index, :new, :create], controller: 'processors' do
member do
get :modify, :action => :edit, :force_schema_update => true
end
resources :items do
member do
get :copy
end
end
Finally, in the items_controller.rb, I have:
def copy
#overlay_title = "Copy #{#processor.item_name}"
#processor.properties.each do |property|
unless #item.property_values.collect{|a| a.property_id}.index(property.id)
#item.property_values << SignalProcessor::PropertyValue.new(property: property)
end
end
respond_to do |format|
format.js
end
end
The problem is that for some reason item doesn't seem to get passed to the copy function in items_controller.rb, so every time I click the copy button, I get the error:
undefined methodproperty_values' for nil:NilClass`
for the line:
unless #item.property_values.collect{|a| a.property_id}.index(property.id)
What am I doing wrong? Why isn't item getting passed?
#item is an instance variable, and its value is nil unless you set otherwise. It's what we have here - you don't set #item anywhere in this action, which is why it's nil. You should probably (assuming you have corresponding Item model) type:
#item = Item.find(params[:id])
before you'd like to use #item value.
First, you need to amend your form view to pass the Item id, which you can use to look up the corresponding model object in your controller:
%span.btn.btn-mini.btn-inverse= link_to copy_image, copy_campaign_signal_processor_item_path(#campaign, #processor, item.id) ,data: { toggle:"modal",target: "#myModal"}, remote: true
In your form, you're passing three arguments to your path, but you haven't named them in your route yet. You can name the parameters you'll be passing to the copy route within the :items resource block:
resources :items do
member do
get 'copy/:campaign/:processor/:item_id', :action => 'copy'
end
end
Then, in your controller, you can look up your Item by ID:
def copy
#item = Item.find(params[:item_id])
...
end

How to make a Controller Function call from Views Template in rails?

I am new to Rails and have a function in product_controller.rb
def detach
#product.photo = nil
#product.save
end
now I want to call this method from views file show.html.erb so the method get executed. How to do it ? I can see the 7 methods do get called through .find(params[id]) but that is also not clear to me.
You'll need to add a route, something like this in routes.rb:
resources :products do
member do
get 'detach' # /products/:id/detach
end
end
That will give you detach_product_path(#product) which you can use in your view. You'll probably also want a redirect in the detach method:
def detach
#product = Product.find(params[:id])
#product.photo = nil
if #product.save
redirect_to #product, notice: 'Photo was detached!'
end
end
Try changing as follow
<%= link_to 'detach_image', product_detach_path(#product) %>
I would suggest you to have a look at guides.rubyonrails.org/routing.html.
you can do as follow,
you can use match
match '/update_profile', :to => 'users#update_profile'
or
resources :users do
get 'update_profile', on: :member
end
and then you would definitely have method in your users controller
def update_profile
#user = User.find(params[:id])
if #user.save
redirect_to #user, notice: 'user updated successfully!'
end
end
I have fixed the Simon answer. However, you are still facing the problem because you are not passing the product with the path:
<%= link_to 'detach_image', detach_product_path %>
You need to pass the product to the action:
<%= link_to 'detach_image', detach_product_path(#product) %>
Otherwise, the Product.find(params[:id]) will not find any product, and the #product will get empty...
Edit to reply your questions:
1 - product_detach_path is a helper for the action detach in the controller product. There is also the product_detach_url, which does the same thing, but also includes the current host, port and path prefix. More details here.
However, it does not pass any param, so Product.find(params[:id]) cannot find the product. For this reason, you must specify what product are you trying to find. #product is defined in the show action, so it is available in your view, but you could send any other product for the detach action.... maybe the first one: product_detach_path(Product.first)
2 - the resources :products generates seven default routes: index, new, create, show, edit, update and destroy.
In order to add more routes to it, you can use member or collection. Basically, member will add a route to a product (products/1/detach), while collection will add a route to the controller, like index (products/detach). More information here.
I hope it helps...

Resources