Why Don't my Parameters get Passed in Ruby on Rails? - 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

Related

ActiveRecord::RecordNotFound "can't find without id"

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

Rails: How to review content before submit/save?

I know it is simple but I can't get my head around a solution.
It is a job board site. Lets say it's functionality similar to this site. When a user fill all required information and click "To next step" or "Preview", another page loads with all filled data. That page is similar to the final page when data is saved.
When user on preview page, it can go forward and submit the page (in this case it will be saved to DB). Or, click back to Edit the job.
I tried the following::
Within _form.html.erb I added a preview button
<%= f.submit "Preview", :name => 'preview' %>
Within JobControllers I altered create method
def create
if params[:preview]
#job = Job.new(jobs_params)
render 'jobs/preview'
else
#job.save
end
end
Created a Preview view /jobs/preview.html.erb
Now I have 2 problems.
1- Within my preview page, I have an edit button like so: <%= link_to "Edit Job", edit_job_path(#job) %>. But I have an error because I can't find #job. Error says: No route matches {:action=>"edit", :controller=>"jobs", :id=>nil} missing required keys: [:id]
SOLUTION Changed like to <%= link_to 'Back to edit', 'javascript:history.go(-1);' %>
2- How I would submit and add to my DB all information on preview page?
Thank you.
Once I've given a similar task. What I've done is to save records, but not to publish. In my index (resource listing) action of relevant controller, I only fetch published records. Also show action prechecks if that record's published attribute is set to true.
What was my model/controllers looked like before
#model
class Book < ActiveRecord::Base
...
scope :active, -> { where(published: true).some_other_queries }
self.active?
(published && some_other_requirements)
end
...
end
#controller
def index
#books = Book.active
...
end
def show
if #book.active?
render 'show'
...
else
...
end
end
First added a secret key for previews.
#model
def secret
#some custom random key generation
# e.g. Digest::MD5.hexdigest("#{id}_#{ENV['RAILS_SECRET']}")
end
Then added preview action to controller
def preview
# i don't check if the record is active.
# also added a security layer, to prevent irrelevant guys to view
# that record
if #book.secret == params[:secret]
render 'show'
else
...
end
end
In dashboard
...
= link_to "Preview", preview_book_path(book, secret: book.secret)
...
then added a member route
#routes
resources :books do
get :preview, on: :member
end
When I have to do something like this what I normally do is create a review table in my app. This table looks just like the table that is going to saving to.
When they press the "Approved" or "Save" button just populate the new table with the proper data.
I like to create a routes to handle this
resources :something do
match 'move_to_something_else' => 'somethings#move_to_something_else', as: :move_to_something_else, via: :all
end
Now on the controller we can do the following:
def move_to_something_else
#something = Something.find(params[:id])
#something_else = SomethingElse.new
#something_else.name = #something.name
....
#something_else.save
redirect_to something_else_path(#something_else)
end
Alternative you could add a state to your table with the default value of 'draft'
# config/routes.rb
resources :something do
match 'published' => 'somethings#published', as: :published, via: :all
end
# Controller
def published
#something = Something.find(params[:id])
#something.state = 'published'
#something.save
redirect_to something_path(#something)
end

Redirecting to another view in rails

I'm trying to build a link shortener. The intended behavior is that on the first page (new) the user inserts his long link and presses a button, then he gets redirected to an another page called result, where a preset message will be waiting for him, along with both his short and long link.
I'm struggling with controllers, however, as no matter what I do something always comes wrong. Right now my controller looks like this:
class UrlsController < ApplicationController
def new
#short_url = Url.new
end
def create
#short_url = Url.new(url_params)
if #short_url.save
flash[:short_id] = #short_url.id
redirect_to "/urls/result"
else
render action: "new"
end
end
def show
Url.find(params[:id])
##short_url_yield =
redirect_to #short_url.url
end
def result
end
private
def url_params
params.require(:url).permit(:url)
end
end
And the routes.rb:
Rails.application.routes.draw do
resources :urls, :only => [:show, :new, :create, :result]
get 'urls/result' => 'urls#result'
root to: redirect('/urls/new')
end
When I submit the link, however, rails returns the following error:
Couldn't find Url with 'id'=result
Extracted source (around line #17):
def show
Url.find(params[:id])
##short_url_yield =
redirect_to #short_url.url
end
It seems I don't understand the logic behind it. What's going wrong? Isn't the show bit supposed to be a redirect that happens when I click the shortified link?
Rails routes have priority in the order they are defined. Since your SHOW route declaration is before get 'urls/result' => 'urls#result' the url gets matched as /urls/id=result.
Simply move your custom route above the resources block or use a collection block.
resources :urls, :only => [:show, :new, :create, :result] do
collection do
get 'result'
end
end
Using the collection and member blocks tells Rails to give priority to the routes inside over the normal CRUD actions such as show.

Why does the params "id" key change from params[:id] into params[:model_id]?

When I go to the characters controller, show action, all the normal params[:id] is as how it should be according to REST.
In the show view, I render a partial. In that partial, I have a link that goes to the vote_socionics action. This action is defined under a socionics_votes module, which gets included by the characters controller. (I have it set up this way because I have other controllers that also include this module).
My problem is that when I click on this link, and it goes to the set_votable private method within the socionics_votes_module.rb file, the params[:id] is no longer present. Using pry, I found that it actually turns into params[:character_id]
Questions:
1) Why does this happen (is it because it goes to a "different" controller, even if it's a module?)
2) How do I work around this? I would think that it would be more elegant to have it be params[:id], instead of having to do an if-else to account for both keys.
characters_controller.rb
class CharactersController < ApplicationController
include SocionicsVotesModule
def show
#character = Character.find(params[:id])
end
characters/show.html.haml
= render partial: 'votes/vote_socionics',
locals: { votable: #votable, votable_name: #votable_name, socionics: #socionics }
_vote_socionics.html.haml
= link_to content_tag(:div,"a"), send("#{votable_name}_vote_socionics_path", votable, vote_type: "#{s.type_two_im_raw}"),
id: "vote-#{s.type_two_im_raw}",
class: "#{current_user.voted_on?(votable) ? 'voted' : 'not-voted'}",
method: :post,
data: { id: "#{s.type_two_im_raw}" }
socionics_votes_module.rb
module SocionicsVotesController
extend ActiveSupport::Concern
included do
before_action :set_votable
end
private
def set_votable
votable_constant = controller_name.singularize.camelize.constantize
#votable = votable_constant.find(params[:id]) # This is where it fails, since there is no params[:id], and rather, params[:character_id]
end
def set_votable_name
#votable_name = controller_name.singularize.downcase
end
routes.rb
concern :socionics_votes do
post 'vote_socionics'
end
resources :characters, concerns: :socionics_votes
resources :celebrities, concerns: :socionics_votes
resources :users, concerns: :socionics_votes
The URL of the link in the partial when hovered over.
localhost..../characters/4-cc/vote_socionics?vote_type=neti
Something like .find(params[:id] || params[:"#{#votable_name}_id"]) didn't work, and seems silly.
You need to add the vote_socionics route as a member of the resource:
concern :socionics_votes do
member do
post 'vote_socionics'
end
end
This way the id parameter gets set correctly

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