Weird values in params when passing nil as foreign key - ruby-on-rails

I'm having some weird problems with rails params. I have two models: Page and NewsItem.
Page: has_many :news_items
and
NewsItem: belongs_to :page
In my form for creating new news_item I have a list of radio buttons with which I can select a page to which newly created item will belong.
Everything is working correctly so far. What is bugging me is:
When I select first option (page has no parent -> value passed should be nil) the value actually passed in parameters for page_id is "on"!
So I have few questions:
Since I need to do some checking of the value of params for proper redirecting, I'm wondering can I count on page_id using value "on" to represent nil? Is this a standard way in rails to represent nil or is this specific to my setup? Is there a chance that this will change in future versions?
As you can see in hash from console on the bottom of the post, after selecting nil as a value to pass as foreign key (page_id), the value actually set as page_id is not nil but 0. Is this a good way of stating that this item does not belong to a page, or should I try to force the value to be nil? Should I be passing a value of zero instead of nil?
Is there a chance for a table to have a row with primary id of zero?
I'm running this in development, on rails 4.0.4 with sqlite
Here is the part of the form with radio buttons:
<ul>
<li>
<%= f.radio_button :page_id, nil %>
<%= f.label :page_id, t('page_no_parent'), value: nil %>
</li>
<% #parents.each do |page| %>
<li>
<%= f.radio_button :page_id, page.id %>
<%= f.label :page_id, page.name, value: page.id %>
</li>
<% end %>
</ul>
Here is the actual params hash from request:
Started PATCH "/hr/admin/news_items/37" for 127.0.0.1
Processing by Admin::NewsItemsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"blablablablablaabllba",
"commit"=>"Save content to page", "news_item"=>{"name"=>"test new",
"title"=>"", "body"=>"", "author"=>"", "page_id"=>"on"},
"locale"=>"hr", "id"=>"37"}
And here is item hash from console:
2.0.0p353 :124 > NewsItem.find(37)
NewsItem Load (0.3ms) SELECT "news_items".* FROM "news_items" WHERE "news_items"."id" = ? LIMIT 1 [["id", 37]]
=> #<NewsItem id: 37, name: "test new", url: nil, title: "", body: "", author: "",
page_id: 0, user_id: 7, created_at: "nvm", updated_at: "nvm">
Please note that I tried to google this, but couldn't find anything...
UPDATE:
The way i realized this was happening was because I have following routes:
resources :pages do
resources :news_items, except: [:index, :show]
end
resources :news_items
I was trying to enable CRUD for :news_items both with and without page_id in url. So after a delete I was trying to redirect to a proper route depending on values in params.
if params[:news_item].key? :page_id and not params[:news_item][:page_id].nil?
admin_page_path(Page.find(params[:news_item][:page_id]))
else
admin_news_items_path
end
So if :page_id is not nil route to corresponding page, else route to NewsItem#index.
But I kept getting an error saying:
ActiveRecord::RecordNotFound in Admin::NewsItemsController#update
Couldn't find Page with id=on
If I pass zero instead of nil, everything works as expected. Is this a good way to represent that a row does not have foreign key?

Related

Rails no route matches controller

I am trying to pass both a user id, and a test id to a controller using link_to. Below is my code:
<%= link_to(test.name, user_test_result_path(:userd_id => 1, protocol.id)) %>
and below are my routes:
but I keep getting the following error:
Why is it saying that no route matches :action => show and :controller=>"test_results when according to my routes it does exist?
Dude. It says userd_id here:
<%= link_to(test.name, user_test_result_path(:userd_id => 1, protocol.id)) %>
Spelling matters!
Also, where is that:
{9=>2...}
coming from in your params? I'm guessing you'll have more luck if you do something like:
<%= link_to(test.name, user_test_result_path(id: protocol.id, user_id: 1)) %>
You shouldn't be passing a hash to your path helper. If your path has two segments, :user_id and :id, you would simply invoke helper_name(user_id, id), not helper_name(user_id: user_id, id).
In your case you should be calling
user_test_result_path(1, protocol.id)

Rails 4 - trouble with two associations, same model

I'm trying to make a simple movie database using Rails 4.0.0 as a learning project. I'm particularly interested in using scaffolding as much as possible, as this is one of the features that drew me to RoR in the first place.
Yes, I do realize the potential risks (Box 1.2.Scaffolding: Quicker, easier, more seductive), but I promise I won't have my project go public before I really understand what's going on beneath the hood. Right now I'm more in "evaluating technologies for my next super duper project"-mode.
Here's what I've got so far:
rails g scaffold Person name:string
rails g scaffold Movie name:string
Now, I could've done something like
rails g scaffold Movie name:string person_id:integer
instead, but I want a movie to be associated with both a director and an actor. (Next step is to make an association that relates multiple actors to a single movie, but I'm not quite there yet.)
So, I headed over to the blog post Creating Multiple Associations With the Same Table, describing pretty much what I need. It's a somewhat old post, so things might have changed now - I don't know. Anyway. This how I changed the models:
class Person < ActiveRecord::Base
has_many :movies
end
and
class Movie < ActiveRecord::Base
belongs_to :director_id, :class_name => 'Person', :foreign_key => 'person_id'
belongs_to :actor_id, :class_name => 'Person', :foreign_key => 'actor_id'
end
and finally the magical
rake db:migrate
Starting the WEBrick by running rails s in the console, I open my browser and start registering people
The time has come to start registering movies. According to previous questions here and here I have to make a migration script in order to create the necessary database fields. So this is what I did:
rails g migration AddPersonIdsToMovies director_id:integer actor_id:integer
I also updated the app/views/movies/_form.html.erb to
<%= form_for(#movie) do |f| %>
<% if #movie.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#movie.errors.count, "error") %> prohibited this movie from being saved:</h2>
<ul>
<% #movie.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :director_id %><br>
<%= f.select :director_id, Person.all.collect {|x| [x.name, x.id]}, {}, :multiple => false %>
</div>
<div class="field">
<%= f.label :actor_id %><br>
<%= f.select :actor_id, Person.all.collect {|x| [x.name, x.id]}, {}, :multiple => false %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
When I create a new movie, the view shows up fine and the select inputs works fine. However, the data in the director and actor field isn't persisted. I ran rails console and looked at the newly created movie:
irb(main):004:0> mov = Movie.first
Movie Load (0.2ms) SELECT "movies".* FROM "movies" ORDER BY "movies"."id"
ASC LIMIT 1 => #<Movie id: 1, name: "No such movie", created_at:
"2013-08-02 17:02:12", updated_at: "2013-08-02 17:02:12",
director_id: nil, actor_id: nil>
which is kind'a disappointing with no director or actor info.
Update
Based on #Mattherick's suggesition, I edited the private part of the movies_controller.rb to this:
# Never trust parameters from the scary internet, only allow the white list through.
def movie_params
params.require(:movie).permit(:name, :director_id, :actor_id)
end
Unfortunately, when I post a new movie I get
Person(#70319935588740) expected, got String(#70319918738480)
Extracted source:
# POST /movies.json
def create
#movie = Movie.new(movie_params)
respond_to do |format|
if #movie.save
and the request data goes as
{"utf8"=>"✓",
"authenticity_token"=>"???",
"movie"=>{"name"=>"Romantic Comedy",
"director_id"=>"2",
"actor_id"=>"1"},
"commit"=>"Create Movie"}
Update 2
I tried to create a new Movie in the rails console, like this:
irb(main):001:0> movie = Movie.new(name: "My fine movie", director_id: "1", actor_id: "2")
ActiveRecord::AssociationTypeMismatch: Person(#70311109773080) expected, got String(#70311102311480)
which is what you'd expect from the POST to the controller. This made me test what happened if I excluded the quotation marks for director_id and actor_id. So I did
irb(main):005:0> movie = Movie.new(name: "My fine movie", director_id: 1, actor_id: 2)
ActiveRecord::AssociationTypeMismatch: Person(#70282707507880) expected, got Fixnum(#70282677499540)
Still using the console, I decided to create an actor and a director
director = Person.new(name: "Woody Allen")
director.save
actor = Person.new(name: "Arnold Schwarzenegger")
actor.save
and then I did
movie = Movie.new(name: "I'd like to see that", director_id: director, actor_id: actor)
movie.save
which worked like a charm (output omitted for brevity). So the whole question boils down to "How can I pass a Person as the argument to director_id and actor_id through the web interface?"
If I had a single field in Movies called person_id: integer, I believe that rails would've inferred that I'm not trying to pass a string containing the id of a person, but rather I'm trying to pass an entire person object.
Update 3
I tested my suspicion that rails understands how to deal with posts when the foreign key column is named after the pattern [table]_id. So I created a new project with a Continent model and a Country model, where rails g scaffold Country name:string continent_id:integer. I changed my Country view to include
<div class="field">
<%= f.label :continent_id %><br>
<%= f.select :continent_id, Continent.all.collect {|x| [x.name, x.id]} %>
</div>
instead of the default numeric field. The continent_id is still posted a string:
Started POST "/countries" for 127.0.0.1 at 2013-08-03 10:40:40 +0200
Processing by CountriesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"???", "country"=>{"name"=>"Uganda", "continent_id"=>"4"}, "commit"=>"Create Country"}
yet rails understood that continent_id was the identifier of an entry in the Continent table.
Sadly, the inferrer mechanism doesn't work in my original case, as I have two associations from Movie to the Person table. I need to somehow make sure rails understand that there is a mapping from director_id to a Person.
Update 4
According to some sources, it seems as I need to refine the Person model further. So I did
class Person < ActiveRecord::Base
has_many :directed_movies, :class_name => 'Movie', :foreign_key => 'director_id'
has_many :acted_movies, :class_name => 'Movie', :foreign_key => 'actor_id'
end
but still not fixing my problems.
I'm kind'a lost. Can anyone give me a few pointers on what I'm missing here? Can anyone help me map from director_id to person_id? Thanks!
Ok, so I finally got it. I don't think this is the correct way to go about this, but at least it solved the problem. As I mentioned in update 2, I was able to create a Movie object in the irb, and so I asked the question "How can I pass a Person as the argument to director_id and actor_id through the web interface?"
According to the sources here and elsewhere, rails should've understood the has_many and belongs_to class methods. However, I just can't seem to get it to work.
So I hacked the create method in movies_controller.rb to read like this:
def create
director = Person.where(:id => movie_params[:director_id]).first
actor = Person.where(:id => movie_params[:actor_id]).first
#movie = Movie.new(name: movie_params[:name], director_id: director, actor_id: actor)
respond_to do |format|
if #movie.save
format.html { redirect_to #movie, notice: 'Movie was successfully created.' }
format.json { render action: 'show', status: :created, location: #movie }
else
format.html { render action: 'new' }
format.json { render json: #movie.errors, status: :unprocessable_entity }
end
end
end
This is certainly not as elegant as I'd like it to be, and I don't think it is the RoR way to do things. Unfortunately it is the only thing I got working so far, but if anyone else can make a multiple association to the same model using rails 4, please do give me a heads up! :)

How to display Rails select field values rather than stored integers in other views

I'm using a select field in a Rails app that is NOT tied to a related model, but stores integer values for a static series of options , i.e.,
<%= select (:this_model, :this_field, [['Option1',1],['Option2',2],['Option3',3],['Option4',4]] ) %>
In a show/ index view, if I want to display the option text (i.e. Option1, Option2, etc) rather than the integer value stored in the database, how do I achieve this?
Thanks for helping a noob learn the ropes!
EDIT
Based on Thorsten's suggestion below, I implemented the following. But it is returning nil, and I can't figure out why.
Invoice model:
##payment_status_data = { 1 => "Pending Invoice" , 2 => "Invoiced" , 3 => "Deposit Received", 4 => "Paid in Full"}
def text_for_payment_status
##payment_status_data[payment_status]
end
Invoice show view:
Payment Status: <%= #invoice.text_for_payment_status %>
In the console:
irb > i=Invoice.find(4)
=> [#<Invoice id: 4, payment_status: 1 >]
irb > i.text_for_payment_status
=> nil
I've tried defining the hash with and without quotes around the keys. What am I missing?
something like this would work:
<%= form_for #my_model_object do |form| %>
<%= form.label :column_name "Some Description" %>
<%= form.select :field_that_stores_id, options_for_select({"text1" => "key1", "text 2" => "key2"}) %>
<% end %>
Update
If you later want to display the text you can get it from a simple hash like this:
{"key1" => "text 1", "key2" => "text2"}[#my_object.field_that_stores_id]
But you better store this hash somewhere in a central place like the model.
class MyModel < ActiveRecord
##my_select_something_data = {"key1" => "text 1", "key2" => "text2"}
def text_for_something_selectable
##my_select_something_data[field_that_stores_id]
end
end
Then you can use it in your views like
#my_object.text_for_something_selectable
There are many possible variations of this. But this should work and you would have all information in a central place.
Update
Ok, I used something similar for our website. We need to store return_headers for rma. Those need to store a return reason as a code. Those codes are defined in an external MS SQL Server Database (with which the website exchanges lots of data, like orders, products, and much more). In the external db table are much more return reasons stored than I actually need, so I just took out a few of them. Still must make sure, the codes are correct.
So here goes he model:
class ReturnHeader < AciveRecord::Base
##return_reason_keys = {"010" => "Wrong Produc",
"DAM" => "Damaged",
"AMT" => "Wrong Amount"}
def self.return_reason_select
##return_reason_keys.invert
end
def return_reason
##return_reason_keys[nav_return_reason_code]
end
end
Model contains more code of course, but that's the part that matters. Relevant here is, that keys in the hash are strings, not symbols.
In the views i use it like this:
In the form for edit:
<%= form_for #return_header do |form| %>
<%= form.label :nav_return_reason_code "Return Reason" %>
<%= form.select :nav_return_reason_code, options_for_select(ReturnHeader.return_reason_select, #return_header.nav_return_reason_code) %>
<% end %>
(Maybe no the most elegant way to do it, but works. Don't know, why options_for_select expects a hash to be "text" => "key", but that's the reason, why above class level method returns the hash inverted.)
In my index action the return reason is listed in one of the columns. There I can get the value simply by
#return_headers.each do |rh|
rh.return_reason
end
If you have trouble to get it run, check that keys a correct type and value. Maybe add some debug info with logger.info in the methods to see what actual data is used there.

Modifying attributes on the join model with accepts_nested_attributes_for

Simply, a Contact can have various associated Time Windows, which may or may not be Active as a Schedule. To wit:
Models
class Contact < ActiveRecord::Base
has_many :schedules
has_many :time_windows, :through => :schedules
accepts_nested_attributes_for :schedules, :allow_destroy => true
end
class TimeWindow < ActiveRecord::Base
has_many :schedules
has_many :contacts, :through => :schedules
end
class Schedule < ActiveRecord::Base
belongs_to :contact
belongs_to :time_window
end
View
<% TimeWindow.all.each do |tw| %>
<% schedule = Schedule.find_by_contact_id_and_time_window_id(#contact.id, tw.id)
schedule ||= Schedule.new %>
<p>
<%= f.label tw.description %>
<%= hidden_field_tag "contact[schedules_attributes][][id]", schedule.id %>
<%= check_box_tag "contact[schedules_attributes][][time_window_id]",
tw.id, #contact.time_windows.include?(tw) %>
<%= check_box_tag "contact[schedules_attributes][][active]", nil,
schedule.active %>
</p>
<% end %>
This submits something like this:
Parameters: { "commit" => "Update", "contact" => {
"group_ids" => ["2"], "enabled" => "1",
"schedules_attributes" => [ { "time_window_id"=>"1", "id"=>"46"},
{ "time_window_id" => "2", "id" => "42", "active" => "on" },
{ "time_window_id" => "3", "id" => "43"},
{ "time_window_id" => "4", "id" => "44", "active" => "on"}],
"last_name" => ...
The update action in the controller is basically stock, except to handle another instance of another related model which I coded using the "Handling Multiple Models" example from the Advanced Rails Recipes book.
According to this API doc, I think the above ought to work. However, nothing about the Schedules is getting updated. This shows up in the server log:
[4;35;1mSchedule Update (0.2ms)[0m [0mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 42[0m
[4;36;1mSchedule Update (0.1ms)[0m [0;1mUPDATE `schedules` SET `updated_at` = '2010-09-30 20:39:49', `active` = 0 WHERE `id` = 44[0m
(NetBeans is giving me those stupid "[0m"'s in the output. I don't know what's wrong there.)
The SQL shows that the "active" boolean field is getting set to 0 where checked. How do I get this to correctly set the active bit?
As a followup, how would I organize this to get rid of the Schedule "connection" at all? I'm thinking I need to submit a :_delete with the Schedule from the form, but how would I do that conditionally when a checkbox is involved?
Thanks for any help you can provide. Rails is turning out to be a vast subject for me, and I want to do it "right." I'm really close here, but there's got to be a way to make this -- not just correct -- but elegant. The view code just feels way too cumbersome to be proper Rails. ;-)
I've kept trying different approaches to this problem, and I've come up with this, which works. Mostly. The only problem is that it doesn't handle NOT having a "Schedule" for each "Time Window". The form will render, and I'll get a disabled check_box (to prevent me from trying to delete something that isn't there), but I don't have a way to add it back, and submitting without it throws off the params hash (and causes Rails to give me an "Expected Hash (got Array)" error)
<% TimeWindow.all.each do |tw| %>
<% schedule = Schedule.find_by_contact_id_and_time_window_id(#contact.id, tw.id)
schedule ||= Schedule.new %>
<% f.fields_for "schedules_attributes[]", schedule do |sf| %>
<p>
<%= sf.label tw.description %>
<%= sf.hidden_field :id %>
<%= sf.check_box :_destroy, :disabled => schedule.new_record? %>
<%= sf.check_box :active %>
</p>
<% end %>
<% end %>
Note that the "schedules_attributes[]" array will automatically give you an existing ID within the braces in your HTML (which is nice), but the _attributes hash is expecting an "id" alongside the other attributes in order to make sense of the sub-hashes.
One of the big lessons I've learned here is that the "check_box_tag" method doesn't (seem to) give me a paired-up hidden field for Rails to parse in the unchecked case. I would have expected this. Adding one in by hand made a mess, which led me to finally giving into the "fields_for" method, and trying many incarnations before finding the appropriate syntax to get what I wanted out of it.
I've realized that my model isn't quite appropriate in this setup, so I'm going to change it, but I was so close to this answer, I wanted to at least get to the point of being able to see the end before I moved on.

'Unknown key(s)' ArgumentError

I'm working on a moderating feature for my application, which is based on a basic scaffold structure. What I need, is to edit several records with the boolean parameter publised on false. In moderate.html I'm getting the list of all unpublished entries with the ability to change their parameters which, what and published. The error appears, when I'm trying to save the changes through the complete action.
ArgumentError in NamesController#complete
Unknown key(s): 7, 1, 4
The "7, 1, 4" are id of my unpublished records.
Here are the parts of my code:
#names_controller.rb
def moderate
#names = Name.find(:all, params[:name_ids], :conditions => {:published => false})
respond_to do |format|
format.html { render :action => "moderate" }
format.xml
end
end
def complete
#names = Name.find(params[:name_ids])
#names.each do |name|
name.update_attributes!(params[:name].reject { |k,v| v.blank? })
end
flash[:notice] = "Updated records!"
redirect_to names_path
end
#moderate.html.erb
<% form_tag complete_names_path do %>
<% #names.each do |name| %>
<fieldset>
<% fields_for "name_ids[#{name.id}]", name do |name_fields| %>
<%= name_fields.text_field :which %>
<%= name_fields.text_field :what %>
<%= name_fields.check_box :published %>
<% end %>
</fieldset>
<% end %>
<%= submit_tag "Ok" %>
<% end %>/
#routes.rb
ActionController::Routing::Routes.draw do |map|
map.connect 'moderate', :controller => 'names', :action => 'moderate'
map.resources :names, :collection => { :complete => :put}
map.root :names
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
I understand, that there's something wrong with the name_ids, but don'nt understand, what should I do.
Thank you in advance.
ruby 1.8.7 (2009-06-12 patchlevel 174)
[universal-darwin10.0] Rails 2.3.5
Rails log for moderate and complete actions:
Processing NamesController#moderate (for 127.0.0.1 at 2010-10-16 21:36:42)
[GET] [4;35;1mName Load (0.6ms)[0m
[0mSELECT * FROM "names" WHERE
("names"."published" = 'f') [0m
Rendering template within
layouts/names Rendering names/moderate
Completed in 12ms (View: 7, DB: 1) |
200 OK [http://localhost/moderate]
Processing NamesController#complete
(for 127.0.0.1 at 2010-10-16 21:36:49)
[POST] Parameters: {"commit"=>"Ok",
"authenticity_token"=>"CtmsjIavksOMSIArrdovkkzuZzHVjkenFFMO5bHIvgg=",
"name_ids"=>{"7"=>{"published"=>"0",
"what"=>"Партия", "which"=>"Крутая"},
"1"=>{"published"=>"1",
"what"=>"Россия", "which"=>"Единая"},
"4"=>{"published"=>"0",
"what"=>"Организация",
"which"=>"Молдавская"}}}
[4;36;1mName Load (0.4ms)[0m
[0;1mSELECT * FROM "names" WHERE
("names"."id" IN (7,1,4)) [0m
NoMethodError (You have a nil object
when you didn't expect it! You might
have expected an instance of Array.
The error occurred while evaluating
nil.reject):
app/controllers/names_controller.rb:47:in
complete'
app/controllers/names_controller.rb:46:in
each'
app/controllers/names_controller.rb:46:in
`complete'
Rendered rescues/_trace (110.3ms)
Rendered rescues/_request_and_response
(0.5ms) Rendering rescues/layout
(internal_server_error)
Likely you need to get just the keys from the name_ids hash. Try:
#names = Name.find(params[:name_ids].keys)
A separate problem is your reference to params[:name], which is nil. Did you mean (EDIT: use to_s to match the params key, lol):
#names.each do |name|
name.update_attributes!(params[:name_ids][name.id.to_s].reject { |k,v| v.blank? })
end
EDIT (brief-ish explanation):
What was happening was that you had a nested hash in the params, params[:name_ids]. It looked like:
"name_ids"=>{"7"=>{"published"=>"0", "what"=>"Партия", "which"=>"Крутая"}, "1"=>{"published"=>"1", "what"=>"Россия", "which"=>"Единая"}, "4"=>{"published"=>"0", "what"=>"Организация", "which"=>"Молдавская"}}
An ActiveRecord 'find' method can take an array of ids, but not a hash of values. What you were originally submitting to 'find' in this line:
#names = Name.find(params[:name_ids])
...was the value for params[:name_ids]:
{"7"=>{"published"=>"0", "what"=>"Партия", "which"=>"Крутая"}, "1"=>{"published"=>"1",
"what"=>"Россия", "which"=>"Единая"}, "4"=>{"published"=>"0", "what"=>"Организация",
"which"=>"Молдавская"}
When what you wanted was:
#names = Name.find(['7','1','4'])
which is what calling params[:name_ids].keys gives you.
The second problem was this line:
name.update_attributes!(params[:name].reject { |k,v| v.blank? })
There was no value ':name' in params, so calling 'reject' on it cause the 'no method' error -- there is no 'reject' method on the nil object. What you wanted was to update the attributes for the 'name' that corresponded to the particular name in the loop. This meant you wanted to get the values out of params[:name_ids][:id] where :id was the id of 'name'.
It all goes back to the way fields_for created the params to begin with. This line:
<% fields_for "name_ids[#{name.id}]", name do |name_fields| %>
meant that params would contain a hash called 'name_ids', with keys corresponding to name.id, that themselves would contain hashes of attributes that ActiveRecord could use in the update_attributes method.
There's a good bit of the famous Rails magic to keep track of in there -- does that help?

Resources