How to use group_by with fields_for in rails? - ruby-on-rails

I have a model with many children (selections). I need to display the children using fields for but I really want to group them based on an attribute on each selection using group_by.
Currently I am using
accepts_nested_attributes_for :selections, :allow_destroy => true
So my form looks a bit like this:
<% form_for #match do |form| %>
<% form.fields_for :selections do |child_form| %>
<%= child_form.object.first_name %>
<%= child_form.check_box '_delete' %>
<%= child_form.label '_delete', 'Remove' %>
<% end %>
<%= form.submit %>
<% end %>
Not quite sure how I could group the :selections using group_by. Any advice?

The question is a little vague. My interpretation is that you want to group similar selections by attribute as they appear in the form. Kind of like this:
form for Match
form for Selections
fields for Selection with attribute A
fields for Selection with attribute A
fields for Selection with attribute A
fields for Selection with attribute B
fields for Selection with attribute B
fields for Selection with attribute C
etc.
The group_by operator is not what you want. It will condense all selections that meet the criteria to a single entry.
The better option would be to use the order option when populating the list for the selection. Might even work out better for you to specify that order in the association. This will do what you want without changing your form.
has_many :selections, :order => "attribute"
But this will cause all your selection queries from match to be ordered by attributes. If this is a problem, you could add a second has_many relationship for selections.
has_many :selections
has_many :grouped_selections, :class_name => "selection", :order => "attribute"
accepts_nested_attributes_for :selections, :grouped_selections :allow_destroy => true
And all that needs to change in your form is <% form.fields_for :grouped_selections %>.

The only option I see is to group them through the association. In your model:
has_many :selections, :order => 'attribute DESC'
It's not the cleanest way to do it (that's how the selections will automatically be ordered throughout the rest of your application, too), but it'll work.

Related

Rails fields_for ordering

I have a form that has some nested fields - ie. User accepts_nested_attributes_for Class.
Class has an end_date datetime field.
I'd like to order the classes by end_date in the editing form. The form has something like this:
<%= f.fields_for :classes do |builder| %>
<%= render "class_fields", {:f => builder} %>
<% end %>
Obviously, they come out in order of the created_at field.
How can I modify this to order them by an arbitrary field?
If you always want classes sorted that way, you can add an :order option to your association. If not, fields_for takes a second argument that's the record(s) to display, so you can pass in your list in whatever order you want.

Rails - how to store "has_many" checkboxes association in database?

I have the table User and the table Categories.
The relation between these two tables:
user has_many :categories
category belongs_to :user
I would like to display on the user's (standard) edit page the list of categories with checkboxes. When this user would check some of the checkboxes, I would like to save them and then display as checked when he open the page the next time.
But how to render the checkboxes in the view? And how to store information about what checkboxes the user checked in database?
My first idea was like to create a table like user_categories with user_id and category_id, but not sure how effective this approach is.
What's the best way to implement this task nowadays?
Not much has changed recently except for the introduction of strong parameters. You still want to use either a has_and_belongs_to_many or has_many :through relationship and then just assign the ids of the relationship directly. You'll need to setup a form that passes an array of ids that are selected so that they come in like {user: {category_ids => [1,2,3]}.
class User < ActiveRecord::Base
has_and_belongs_to_many :categories
end
In the controller
def update
current_user.update(user_params)
end
def user_params
params[:user].permit(
{:category_ids => []}
)
end
you can take the habtm approach.
then in user form partial, add field
<div class="field">
<%= f.label "Categories" %><br />
<% for category in Category.all %>
<%= check_box_tag 'user[category_ids][]', category.id,
#user.category_ids.include?(category.id), :id => dom_id(category) %>
<%= label_tag dom_id(category), category.name, :class => "check_box_label" %>
<% end %>
</div>
where category.name is the field in your category model.
this should work.
then in your user index view,
<% #users.each do |user| %>
<%= user.categories.collect(&:name).join(",") %>
<% end %>
it should show the associated category of the user. dont forget to add :category_ids in your user model attr_acessible field. else mass assingment security will pop up
You probably want to implement a many to many relationship using has_many :through rather than having categories belong to a user (means a category can only ever have one user, is that what you want?
Have a read of has_many :through to get you started. Once you've done that the checkboxes are easily implemented using the collection_check_boxes helper.

Ordering items in a block that have been pulled out of a join model

Below is the block. Group and Workout are models connected with a has_many :through relationship and a GroupsWorkout join model in between.
<% #group.workouts.each do |workout| %>
<%= link_to workout.name, workout %><br />
<% end %>
I want to order workout.name by the workout_order column in the WorkoutsGroup table
normally I would just use Model.order(column_name, DESC) but as far as I can tell, that doesn't work here.
If you want to order the items just in the page before displaying them, then you can sort the workouts collection before iterating like so:
<% #group.workouts.sort_by{|workout| workout.workout_order}.each do |workout| %>
<%= link_to workout.name, workout %><br />
<% end %>
Edit: ah, I think I misunderstood your question. Give me a moment to refine the answer.
You can order your workouts by a column in the join table by defining the order in the association like so:
Group model:
has_many :workouts, :through => :group_workouts, :order => "workoutorder DESC"
You could add order in the Group model:
has_many : workouts, :through => :group_workouts, :order => 'workouts.name'

Rails nested model form is showing random order on child objects

I've followed and used Ryan Bate's nested model form tutorial to create tracks for my releases (in the tutorial it's questions for surveys).
This works really well until I noticed that the order the tracks get added to the DB is seemingly random not as they appear or are entered in the form I need that to be the case.
The tracks are built using the following definition in the release model:
def track_attributes=(track_attributes)
track_attributes.each do |attributes|
tracks.build(attributes)
end
end
Then in the release _form partial I have:
<%= f.fields_for :tracks do |builder| %>
<%= render 'track_fields', :f => builder %>
<% end %>
That pulls in the _track_fields partial, containing:
<%= f.text_field :name, :class => "text" %>
<%= f.text_field :isrc, :class => "text" %>
<%= f.check_box :_destroy %>
etc
Any ideas why the array of tracks is losing the order they were entered?
I am using acts_as_list in the releases_tracks has many through model that works fine, but it takes the order from what's been incorrectly added to the tracks table.
EDIT:
It seems my tracks are being saved with:
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
Not via the track_attributes=(track_attributes) def as I had thought.
Does anyone know how to write a before_save method that will sort the tracks by a position field I've now added to the form?
The order in which SQL queries are executed is random in general. This can apply both to a bulk insert and to a select. If you need your records in a certain order, you must use an ORDER BY clause. Depending on how the records are saved, you might be able to sort on the id, otherwise consider adding a field that represents the list position of each item.

Use accepts_nested_attributes_for to create new records or update existing

Read the big update for the latest information.
Hey everyone,
I've got a many-to-many relationship in a rails app that involves three tables: a user table, an interests table, and a join user_interests table that also has a rating value so a user can rate each of their interests on a 1-10 scale.
I am basically looking for a way for a new user to create their rating when they sign up and edit them at a future date along with any of their profile information at the same time.
I tried to follow this question Rails nested form with has_many :through, how to edit attributes of join model? but the problem I'm having is trying to incorporate a select list into the mix and having multiple interests to rate for the user.
Model Code:
user.rb
has_many :user_interests, :dependent => :destroy
has_many :interests, :through => :user_interests, :foreign_key => :user_id
accepts_nested_attributes_for :user_interests
interest.rb
has_many :user_interests, :dependent => :destroy
has_many :users, :through => :user_interests, :foreign_key => :interest_id, :dependent => :destroy
user_interest.rb
belongs_to :user
belongs_to :interest
View Code:
app/views/user/_form.html.erb
<%= form_for(#user) do |form| %>
... user fields
<%= form.fields_for :user_interests do |ui_form| %>
... loop through ALL interests
<% Interest.all.each do |interest| %>
<%= ui_form.select :rating, options_for_select(1..10) %>
<%= ui_form.hidden_field :interest_id, :value => interest.id %>
<% end %>
<% end %>
<% end %>
I also included the following in the new/edit actions in my controller #user.interests.build.build_interest
The problem I'm running into is that only one interest rating is being passed in the params hash when I want to have multiple. Also I am getting an exception thrown by rails
Interest(#2172840620) expected, got Array(#2148226700)
What tiny detail did I miss or get wrong that is causing the problem?
EDIT:
I found a way to force this to work but it requires manually editing the HTML in chrome developer tools, the :name attribute of my form elements are being generated as user[user_interests_attributes][rating] but if I change it to user[user_interests_attributes][][rating] it will work when I update a record. However I can't manually specify the :name of a form element that is tied to a form object. So what can I do to show that multiple interest ratings are being passed instead of just one that rails thinks?
BIG Update:
I got a semi functional version going with some slight changes:
View code:
<% form.fields_for :user_interests do |ui_form| %>
<p>
<%= ui_form.select :rating, options_for_select(1..5), :selected => :rating %>
<%= ui_form.label :interest_title %>
<%= ui_form.hidden_field :interest_id %>
</p>
<% end %>
Controller code:
def new
#user = User.new
Interest.all.each { |int| #user.user_interests.build({ :interest_id => int.id }) }
end
def edit
#user = #current_user
Interest.unrated_by_user_id(#user.id).each { |int| #user.user_interests.build({ :interest_id => int.id }) }
end
Now I am able to edit and get my user_interests updated or created if no rating exists, but I get an error that user is empty when I try to create a new user. Also I am unable to access any of the interest attributes in the form to display the interest the user is actually rating. Can anyone help with those caveats?
You only need #user.interests.build because its a has_many relationship. build_interest is for when there is a has_one/belongs_to relationship.
When using fields_for :user_interests you're telling the User model that an instance of one or more user_interest objects will be in the parameters hash when the user is created/updated. The form is not creating or updating any user_interests but it is sending back an array of user_interest_attributes hashes that represent the user_interests for the user the form references. This is an array of user_interests rating values for which no user_interests exist as you reference them in the form which is the reason you get the error.
Since you are passing a range to the select form helper you aren't actually providing any interests to the form for selection. The select will set a value for the rating column in the user_interests table with a value between 1 and 10. No user_interest exists for the rating to be set on even if the user_interests table has a rating column.
passing :multiple => true in the options hash of the select tag will create a multiple select list but I don't think that is what you want. I think you want many items on a page the user can put an interest rating on.
If you do want a user to be able to select many interests this is how to use fields_for with accepts_nested_attributes_for on a has_many :through relationship:
<%= form_for(#user) do |f| %>
<% f.fields_for :interest_ids do |interest| %>
<ul>
<% Interest.all.each do |choice,i| %>
<li class="selection">
<%= interest.check_box [], { :checked => f.object.user_interest_ids.include?(choice.id) }, choice.id, '' %>
<%= interest.label [], choice.name %>
</li>
<% end %>
</ul>
<% end %>
<% end %>

Resources