Rails - how to display value through associations in form_for? - ruby-on-rails-3

This is my view:
= form_for(#user) do |f|
= f.autocomplete_field #user.city.name, autocomplete_city_name_users_path
On the second line I am trying to display the association, but I am getting
undefined method `London' for #<User:0x00000129bb3030>
The associations:
User belongs_to :city
City has_one :user
The displayed result in the error message (London) is right, but why I am gettng that error message?

The argument to f.autocomplete_field should be the name of a method. The form builder will send this method to #user to get the correct value. Since the value you're interested in is not in user but in an object owned by user, you have a few options:
Add city_name and city_name= methods to your User class:
# app/models/user.rb
def city_name
city && city.name
end
def city_name=(name)
city.name = name # You'll want to make sure the user has a city first
end
If you don't know how to make sure you have a city, you could create one lazily by changing your city_name= method to this:
def city_name=(name)
build_city unless city
city.name = name
end
Then your form would look like this:
= form_for(#user) do |f|
= f.autocomplete_field :city_name, autocomplete_city_name_users_path
Or you could treat this as a nested object. Add this to User:
accepts_nested_attributes_for :city
And use fields_for in your form:
= form_for(#user) do |f|
= f.fields_for :city do |city_f|
= city_f.autocomplete_field :name, autocomplete_city_name_users_path

Related

Why isn't my search method working in Ruby on Rails?

In my Ruby on Rails application, I have a cinema system and am trying to return the screen a showing is in when a user searches for the showing.
To display the search drop down I am using this code in my _application.html.erb:
<%= render( :partial => '/screen_lookup', :locals => {:showings => #showings = Showing.all, :my_path => '/screens/display_screens_by_showing' })%>
Which renders the search from the _screen_lookup.html.erb:
<%= form_tag my_path, :method=>'post', :multipart => true do %>
<%= select_tag ('showings_id'),
options_from_collection_for_select(#showings, :id, :showing_times, 0 ),
:prompt => "Showings" %>
<%= submit_tag 'Search' %>
<% end %>
And uses the display_screens_by_showing in the screens_controller:
def display_screens_by_showing
#screens = Screen.showing_search(params[:showing_id])
if #screens.empty?
# assign a warning message to the flash hash to be displayed in
# the div "feedback-top"
flash.now[:alert] = "There are no films of that genre."
# return all products, in alphabetical order
#screens = Screen.all
end
render :action => "index"
end
And this searches using the method in the screen.rb model:
def self.showing_search(showing_id)
screen = Showing.where("id = ?", showing_id).screen_id
self.where("id = ?", screen)
end
Now, the problem I am having is that because a showing belongs_to a screen, and a screen has_many showings, I need to be able to search for the showing, and store that showing's screen_id in a variable to search for the screen that showing is in with, which I have tried doing in the model:
screen = Showing.where("id = ?", showing_id).screen_id
self.where("id = ?", screen)
But the error I am getting is:
NoMethodError in ScreensController#display_screens_by_showing
undefined method `screen_id' for #<ActiveRecord::Relation []>
These are the model relationships:
showing.rb:
class Showing < ActiveRecord::Base
belongs_to :screen
end
screen.rb:
class Screen < ActiveRecord::Base
has_many :showings
end
What code will get my search working?
The problem is that where doesn't return a record, it returns a relation that can be enumerated or chained, instead you want to use find or find_by to return a single record, which is kind equivalent to where + first
screen = Showing.find(showing_id).screen_id
which is sort of like doing
screen = Showing.where(id: showing_id).first.screen_id
If you want to pass a hash you can use find_by which will be like this
screen = Showing.find_by(id: showing_id).screen_id
PS:
I'm not sure what you're doing exactly, but i think those two lines can be merged into a single query ( not sure what it should be returning, but I'm assuming a screen )
def self.showing_search(showing_id)
Showing.find(showing_id).screen
end

If a Rails model has_many children, how do I store the id of the first?

I'm new to Rails, so it's possible I'm overlooking something simple. I have a Rails model called a story. Each story has_many segments, and each segment belongs_to a story. I use the same form to create both the story and its first segment by using a fields_for section of the form and setting the story model to accepts_nested_attributes_for :segments. I am currently able to use the form to create both a story and a segment simultaneously.
The problem is that each story also needs to store the id of its first segment, but when I'm saving the story, the segment hasn't yet been saved, so it doesn't yet have an id to store in the story, and I haven't been able to find a handle for the segment after the form is submitted so that I can save the segment first before the story is created. So my question is how do I save a record of the first_segment_id inside the story?
The following code may be relevant:
in app/models/story.rb
class Story < ActiveRecord::Base
attr_accessible :segments_attributes
has_many :segments
accepts_nested_attributes_for :segments
end
in app/models/segment.rb
class Segment < ActiveRecord::Base
attr_accessible :words
belongs_to :story
end
in app/views/stories/_ form.html.erb
<%= form_for(#story) do |f| %>
#...stories fields...
<%= f.fields_for :segments do |segment_form| %>
<div class="field">
<%= segment_form.label :words %><br />
<%= segment_form.text_area :words %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
in app/controllers/stories_ controller.rb
def new
#story = Story.new
#segment = #story.segments.build
# If I try replacing the above with #segment = #story.segments.create
# then I get the error message "You cannot call create unless the
# parent is saved," which is problematic because I need to find some way
# to get the id of #segment to save in the parent story, but the segment
# won't have an id until after it has been saved.
respond_to do |format|
format.html # new.html.erb
format.json { render json: #story }
end
end
def create
#story = Story.new(params[:story])
# #segment.save
# #story.first_segment_id = #segment.id
# If I uncomment the above two lines, I get the error message
# "undefined method `save' for nil:NilClass". It appears that
# #segment hasn't been passed from the "new" method above to
# this method as a handle of the first segment created, so I'm not
# able to save it to get an id for it before saving the story.
# Is there some way to save the segment here?
respond_to do |format|
#...if #story.save...
end
end
The params hash submitted by the form looks something like:
{ "story"=>{ Various_other_story_fields,
"segments_attributes"=>{"0"=>{"words"=>"dfsdsa"}}},
"commit"=>"Create Story"}
Is there a way to save the first segment's id in the story? I think perhaps I need to add a before_create inside my story model instead, but I'm not sure how do to this.
I would approach this differently, adding a numeric sort_order column to Segment so that you're not relying on Segment ids to determine what order they go in. Then you can define something like the following on your Story model rather than explicitly referencing the first segment in the database:
def first_segment
segments.order(:sort_order).first
end
If you're certain you need to store the first segment ID in the story, you can .save the story so that it knows its ID, saves its children, and knows their IDs. Something like:
def create
#story = Story.new(params[:story])
#story.save # Save the story and its child segment so that they both have IDs
#story.first_segment_id = #story.segments.first.id
#story.save
...
end
You should be able to do something like:
class Story < ActiveRecord::Base
attr_accessible :segments_attributes
has_many :segments
has_one :first_segment
accepts_nested_attributes_for :segments
end
def new
#story = Story.new
#segment = #story.segments.build
#story.first_segment = #segment
...
You'll have to add story_id to your segment table.

Rails: Retrieving polymorphic data from nested model

I'm trying to retrieve data for all items from a box, a box can have compartments and I'd like to get all compartment info at the box level. items are made polymorphic as boxes won't necessarily have compartments.
MODEL
class Box < ActiveRecord::Base
has_many :compartments
has_many :items, :as => :itemable
end
In my Controller I can get results back with:
#box = Box.find(params[:id])
#itemable = #box.compartments.first
#itemable = #box.compartments.last
VIEW
<% #items.each do |item| %>
<%= item.name %>
<% end %>
but if I then try
#itemable = #box.compartments
OR
#itemable = #box.compartments.find(:all)
I get the error
undefined method `items' for #<ActiveRecord::Array>
OR
undefined method `items' for #<ActiveRecord::Relation>
Can anyone help with getting results back from all compartments?
So in compartments you have
belongs_to :box
has_many :items, :as => :itemable
Is this the case? #box.compartments should return an array of compartments right? It sounds like items is somehow getting called on #box.compartments somehow

Rails sorting associations with Ransack

first time poster. I am trying to sort a table of users using the Ransack gem and Kaminari for pagination. When I use name, id, etc. sorting works but when I try an association with posts_count, sorting breaks and won't work. Note: in the view, 'u.posts.count' work correctly. I have tried custom scopes in the users model, and creating custom objects for the search params but nothing seems to work. I think I am having trouble either in the default scope or the #search object not having the data. Need help!
Here are some relevant snippets:
models/user.rb
has_many :posts, :dependent => :destroy
models/post.rb
belongs_to :user
default_scope :order => 'post.created_at DESC'
controllers/users_controller.rb
def index
#title = "User Index"
#search = User.search(params[:q]) # Ransack
#total_users = User.all.count
# .per(10) is the per page for pagination (Kaminari).
#users = #search.result.order("updated_at DESC").page(params[:page]).per(10) #This displays the users that are in the search criteria, paginated.
end
views/users/index.html.erb
..
<%= sort_link #search, :posts_count, "No. of Posts" %> #Sort links at column headers
..
<% #users.each do |u| %> #Display everything in the table
<%= u.posts.count %>
<% end %>
You can add a scope to your User model:
def self.with_posts
joins(:posts).group('posts.id').select('users.*, count(posts.id) as posts_count')
end
and use it like this:
#search = User.with_posts.search(params[:q]) # Ransack
then, you can treat posts_count like any other attribute.
I found a solution:
Controller:
def index
sql = "users.*, (select count(posts.id) from posts\
where posts.user_id = users.id) as count"
#search = User.select(sql).search(params[:q])
if params[:q] && params[:q][:s].include?('count')
#users = #search.result.order(params[:q][:s])
else
#users = #search.result
end
.......
end
View:
<th><%= sort_link #search, :count, "posts count" %></th>

grouping and fields_for

I'm trying to create a form which allows me to submit new records for an association where the association inputs are grouped.
class Product < AR::Base
has_many :properties
accepts_nested_attributes_for :properties
end
Note that in the controller a series of properties are built for the product, so #product.properties.empty? # => false.
The below fields_for gives me the correct inputs with names such as product[properties_attributes][0][value].
= form.fields_for :properties do |pform|
= pform.input :value
But as soon as I try and group the association it no longer generates inputs with the correct names:
- #product.properties.group_by(&:group_name).each do |group_name, properties|
%h3= group_name
= form.fields_for properties do |pform|
= pform.input :value
This create inputs which the name attribute like product[product_property][value] when in fact it should be product[property_attributes][0][value] as per the first example.
The Rails documentation suggests you can do this:
= form.fields_for :properties_attributes, properties do |pform|
But this gives an error "undefined method value for Array".
You need to set up like this:
- #product.properties.group_by(&:group_name).each do |group_name, properties|
%h3= group_name
= form.fields_for :properties, properties do |pform|
= pform.text_field :value
It should work fine, since you have accepts_nested_attributes_for :properties rails know that it's supposed to create fields for properties_attributes. Moreover you may want to add
attr_accessible :properties_attributes if you are using one of newest Rails and if you didn't add it to your model yet ;)
Also, if you want to make some decision base on single property you may use the following form as well:
- #product.properties.group_by(&:group_name).each do |group_name, properties|
%h3= group_name
- properties.each do |property|
= form.fields_for :properties, property do |pform|
= pform.text_field :value
Both of them are nicely described here: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for under One-To-Many section
I don't know of a clean 'Rails' solution to this sort of thing, but I often handle these types of situations more manually. eg - Loop through the groups and conditionally show only those properties that are in a particular group.
class Product < AR::Base
has_many :properties
accepts_nested_attributes_for :properties
def group_names
properties.map(&:group_name).uniq.sort
end
end
In the view
- for group_name in product.group_names
= form.fields_for :properties do |pform|
%h3= group_name
- if pform.object.group_name.eql?(group_name)
= pform.input :value
There may be a little overhead with this, getting the list of properties from product repeatedly. You may be able to modify your has_many :properties association to retrieve the properties in group_name order - then you could add the %h3 when it's a new group.
- previous_group_name = nil
= form.fields_for :properties do |pform|
- if pform.object.group_name != previous_group_name
%h3= pform.object.group_name
= pform.input :value
- previous_group_name = pform.object.group_name
Some ideas for you...