Rails fields_for ordering - ruby-on-rails

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.

Related

Form has_many in text_area

I have the following situation:
A Order has many Pages. I want to let the User to paste a bunch (20+) URLs (it's a Page attribute) that they might have in a doc file into a text area.
Right now I am not using a Form associated with an Order object, because I fail to see how I can do a nested form of the URLs if those are inside a text area.
I have seen a similar question has been asked before here: Rails: Using a Textarea for :has_many relationship , but I fail to see how would I code the view and model in order to do so.
So, if I have this:
Order has_many Pages
And a form like this:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls?? %> # This would let the user paste X URLs, which would be
# used to create X Pages associated with the Order.
<% end %>
You could retain the view code that you have:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls %>
#other field and submit button
<% end %>
In your model, you'll need to do the following:
attr_accessor :page_urls
after_validation do
if page_urls
parse_page_urls.each do |url|
pages.create(url: url)
end
end
end
def parse_page_urls
#use regexp to extract urls from page_urls string and return an array of url strings
end
The accessor is defined so that you can use :page_urls in your form_builder. You could set easily validations in your model for :page_urls that way too.
Once order has been validated, it will create page objects according to the number of urls extracted from the page_urls attribute.
You could refer to this for some help with using regexp to extract the urls from the string.
Hope that helps!
This is a job best handled with nested form. It will let you submit attributes of a has_many relationship model from the parent model, like you wish to do. For example, from its docs:
Imagine you have a Project model that has_many :tasks. To be able to use this gem, you'll need to add accepts_nested_attributes_for :tasks to your Project model. If you wish to allow the nested objects to be destroyed, then add the :allow_destroy => true option to that declaration. See the accepts_nested_attributes_for documentation for details on all available options.
This will create a tasks_attributes= method, so you may need to add it to the attr_accessible array (attr_accessible :tasks_attributes).
Then use the nested_form_for helper method to enable the nesting.
<%= nested_form_for #project do |f| %>
You will then be able to use link_to_add and link_to_remove helper methods on the form builder in combination with fields_for to dynamically add/remove nested records.
<%= f.fields_for :tasks do |task_form| %>
<%= task_form.text_field :name %>
<%= task_form.link_to_remove "Remove this task" %>
<% end %>
<%= f.link_to_add "Add a task", :tasks %>
In response to your comment:
In order to do something like that, you would need to do processing in the controller to separate the URL's, then make a new Page object associated with #order object. Unfortunately, there isn't a way to do this without post-processing, unless you do it with JS on the client side with hidden inputs.

Using Rails' form helpers without a model

For various reasons I need to avoid traditional nested forms (nested in the sense of treating the fields like a sub-group of the primary model for the page), but still want to keep fields grouped together with an index-style naming, so I have this:
<%= simple_fields_for :crate_request do |ff| %>
<%= ff.input :_create, :label => "crate needed", :as => :boolean %>
<%= ff.input :details, :as => :text %>
<% end %>
The rendered fields are named as expected (with names like params[:crate_request][:details]) and all looks well until I submit a form with validation errors and it has to come back to re-render. The fields don't prefill with the submitted values stored in the params hash. Although I'm using simple_form, it doesn't seem to just be a simple_form issue. The native Rails helpers appear to do the same.
So the question: is there any way to have the fields automatically pre filled from the params hash again without having to manually set the value of each field from params?
You would just need to pass some object as an extra argument to simple_fields_for.
As the form builder expects the object to have field accessors as methods, but you've only got a hash (params[:create_request]), you can use OpenStruct to create an object which would translate missing method calls to hash lookup.
The final solution then would look something like this:
<%= simple_fields_for :create_request, OpenStruct.new(params[:create_request]) do |ff| %>
...
<% end %>
Replace
<%= ff.input :details, :as => :text %>
by
<%= input :details, :as => :text %>

How to access nested child element in fields_for

I'm trying to access a Hash type of mongoid in fieds_for and I already have a relationship with a model and want to access a hash of that model. Something like:
class Leave
field :leaves_types, :type => Hash
end
class User
has_many :leaves
end
Want to do something like:
form_for #user do |f|
f.fields_for :leaves.leave_types...
How I can achieve this? Thanks in advance.
You should give a block to fields_for. For more information on that method see docs. In your case, first, add this line to your User model:
class User
has_many :leaves
accepts_nested_attributes_for :leaves
end
This is required so that when your form is posted, the attributes coming from the form fields for leaves via params were handled correctly.
Now your template should look like this (for simplicity by now I assume that your Leave also has a simple text field named foo):
<%= form_for #user do |f| %>
...
<%= f.fields_for :leaves do |leave_fields| %>
# Fields for a leave here ----v
Foo: <%= leaves_fields.text_field :foo %>
<% end %>
<% end %>
Or, if you #user.leaves already initialized and you want form builder to put its values to form fields, you have to iterate over #user.leaves, passing each of them to fields_for as second argument:
<%= form_for #user do |f| %>
...
<% #user.leaves.each do |leave| %>
<%= f.fields_for :leaves, leave do |leave_fields| %>
# Note the addition here --^
Foo: <%= leave_fields.text_field :foo %>
<% end %>
<% end %>
<% end %>
But your question has another one inside: you have not a text field, but a hash, and there is no default form input for it (i.e. there is no f.hash_field :leaves_types). So you may want to make it by yourself like suggested in these questions: [1], [2] and [3].
Anyway, having a Hash field seems rather uncommon to me, so maybe Hash can be somehow replaced, say, with another has_many association (not sure), and in this case you will only need another nested fields_for.

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.

How to use group_by with fields_for in 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.

Resources