Maintain the order of an array in rails forms - ruby-on-rails

I have 3 models (Workout, WorkoutSet, WorkoutStep) with the following associations:
A Workout has many sets
A WorkoutSet has many WorkoutSep
And a WorkoutStep has an array of videos (called video_usage[])
Using simple_form and cocoon I made the forms to edit such associations but, at the deepest level (the array of videos of WorkoutStep) by the time I edit a Workout, the order seem not to be constant, being, instead, ordered by (guessing) the last time the attribute has been modified (in this case, modified is changing a property, such as selecting another video).
I want the array of videos to remain constant between edits.
This is how my form looks like:
_workout_sets_fields.html.haml
.nested-fields
= f.simple_fields_for :main_video_usage do |mv|
= render 'main_video_usage_fields', f: mv
.steps{ :style => "margin-left: 680px" }
= link_to_remove_association 'remove step', f
= link_to_add_association 'add video', f, :main_video_usage
_main_video_usage_fields.html.haml
.nested-fields
= f.collection_select :video_id, Video.all.order(:title), :id, :title

You can give an explicit order, use id or created_at if you want to keep the order of creation:
has_many :main_video_usage, -> { order('id asc') }, class_name: 'VideoUsage::Main', as: :parent

Related

Creating a form for a has_and_belongs_to_many relationship

I've read through a lot of posts regarding this topic, but not one actually provides a good answer.
I have a class A and a class B who are related through a has_and_belongs_to_many relationship.
class A < ApplicationRecord
has_and_belongs_to_many :bs
end
class B < ApplicationRecord
has_and_belongs_to_many :as
end
What I need is a form for class A showing a selection field with all the instances of B to select. Also, when editing and instance of A, the form should present a list of the related instances of B and a way to mark them for removal.
I have tried creating the list of existing instances by providing a hidden field with the id for each instance of B. And I have created a selection field using the select_tag. The code looked something like that:
= form_for #a do |f|
.field
= f.label :name
= f.text_field :name
.field
- #a.bs.each_with_index do |b, i|
= b.name
= hidden_field_tag "a[b_ids][#{i}]", b.id
.field
= select_tag "a[b_ids][]", options_from_collection_for_select(#bs, "id", "name")
.actions
= f.submit 'Save'
This worked just fine when creating the new instance of A. But when I tried to edit it, there seems to be a problem making the execution fall back to using POST instead of PATCH. And since with there is now route for POST when editing/updating this obviously ended in an exception.
So I wonder if there is any clean way to create the form I need?

Money Rails Gem - null values

I have monetised two models of my Rails 4 app with Money-Rails gem.
One is called Participants, the other is called Funding. Each of these models is nested inside another model, called Scope. Scope belongs to Project.
The associations are:
Project has one Scope; Scope belongs to Project
Scope has one Participant and has one funding; each of Participant and Funding belong to Scope.
Project accepts nested attributes for Scope. Scope accepts nested attributes for Participant and Funding.
Params for each relevant attribute in Participant and Funding are permitted in the Scope and Project Controllers as well as the models themselves. Params for Scope are permitted in the Scope and Project controllers.
In my Project form, I ask several questions. That form also has nested forms for each of the models which belong to it. Inside the Scope form, I ask users two boolean questions, being: Do you want participants? Do you want funding? Each of these models has a follow up question about participation cost and funding (those attributes are monetised).
If the answer to those questions is true, then I reveal the participant or funding form partial and ask how much money they want.
I have two problems:
First problem: Not null violation
1. If a user says they do want participants, but there is no associated costs, so that the boolean question inside the participant model asking whether there is cost involved with participation, I get an error that says:
ERROR: null value in column "participation_cost_pennies" violates not-null constraint
If a user says they don't want participants in answer to the question asked in the Scope form, I get the same error as in 1 above
Second problem: If I save an amount in the monetised fields, and come back to edit the project form, the form does not show the saved amount in the monetised field - and if you don't reenter it, I get an error saying that it can't be blank.
Does anyone know how to:
make the first problem go away in all circumstances except those when participation costs are actually sought; and
Fix the second problem by displaying the original amount saved when you come back to edit the form? I have tried inserting :selected into my form element, but it doesn't do anything.
My code is as follows:
Inside my Scope form (nested inside my project form):
<%= f.simple_fields_for :scope do |s_all| %>
<%= s_all.input :if_participant, :as => :boolean, :label => false, inline_label: 'Public participants or volunteers' %>
<%= s_all.input :if_funding, :as => :boolean, :label => false, inline_label: 'Funding or expenses' %>
If the answer to these fields is true, then I reveal the partial forms for participant of funding (for whichever is true).
Inside my Participants partial form, I have:
<%= f.simple_fields_for :scope do |participants_s| %>
<%= participants_s.simple_fields_for :participant do |par| %>
<%= f.label 'Are participants reimbursed for their costs?', :class => 'question-project' %>
<%= par.collection_radio_buttons :costs, [[true, ' Yes'], [false, ' No']], :first, :last, {:item_wrapper_class => 'fixradio'}, {:class => "response-project"} %>
<%= f.label 'What amount will you pay for participation costs?', :class => 'question-project' %>
<%= par.select :participation_cost_currency,
options_for_select(major_currencies(Money::Currency.table)), selected: :participation_cost_currency,
label: false,
prompt: "Select your costs currency" %>
<%= par.input :participation_cost, label: false, placeholder: 'Whole numbers only', selected: :participation_cost_pennies, :input_html => {:style => 'width: 250px; margin-top: 20px', class: 'response-project'} %>
For the first problem, you'll want to set a default value for the participation_cost_cents column in a migration:
# in console
> rails g migration change_default_for_participation_cost_cents
# in migration file
class ChangeDefaultForParticipationCostCents < ActiveRecord::Migration
def change
change_column :participants, :participation_cost_cents, :integer, default: 0
end
end
I'm not sure I follow on the second problem though. Maybe you should split the question in two?
A meetup group for Rails has helped me answer this question. The answer is not obvious - especially for newcomers.
My problem was I had an attribute in my database called participation_cost. Monetise then tried to make a method with the same name and that was failing because of the attribute in my table. For others, you don't need the attribute in your database with the name of the field you want to monetise.
Removing that attribute (in my case, participation_cost) solved my problem.

Cocoon Show object instead value

My situation:
"app/models/aircraft.rb"
class Aircraft < ActiveRecord::Base
has_many :aircraft_crew_roles
has_many :crew_roles, through: :aircraft_crew_roles, class_name: 'CrewRole'
end
"app/models/aircraft_crew_role.rb"
aircraft_id
crew_role_id
class AircraftCrewRole < ActiveRecord::Base
belongs_to :aircraft
belongs_to :crew_role
accepts_nested_attributes_for :crew_role, reject_if: :all_blank
end
"app/models/crew_role.rb"
name
class CrewRole < ActiveRecord::Base
end
"app/views/aircrafts/_form.html.haml"
= simple_form_for(#aircraft) do |f|
= f.error_notification
= f.input :name
#crew
= f.simple_fields_for :aircraft_crew_roles do |cr|
= render 'aircrafts/aircraft_crew_role', :f => cr
= link_to_add_association 'add a Crew Role', f, :aircraft_crew_roles, partial: "aircrafts/aircraft_crew_role"
= f.button :submit
"app/views/aircrafts/_aircraft_crew_role.html.haml"
.nested-fields.crew-fields
#crew_from_list
= f.association :crew_role, as: :select, collection: CrewRole.all()
= link_to_add_association 'or create a new Role', f, :crew_role
= link_to_remove_association "remove Role", f
"app/views/aircrafts/_crew_role_fields.html.haml"
.nested-fields
= f.input :role
this is my current situation and almost everything work fine: I can add new crew members to the aircraft and this will create the corresponding entry in the aircraft_crew_role and crew_role tables and if I connect to the database I can see that all the entry are right ( I see all ID and the role in the crew_role table is the one I've used in the form) but if I try use the crew already saved with the previous aircraft the select is populated with object instead of string:
#<CrewRole:0x007fa90d2f8df8>
#<CrewRole:0x007fa90d2f8c90>
#<CrewRole:0x007fa90d2f8b28>
I've already used cocoon in many other places in the app and in all the other places it worked smoothly.
I've compared my code with the example on the cocoon repo but I can't find the problem
You are using simple_form's f.association which will try to guess how to present the CrewRole object. It looks for name, label or plain old to_s ... In your case it is a non-standard field role, so it will just convert the model to string (giving the result you got).
To solve this you would have to rename your column (a bit drastic, but maybe that is what you did when recreating the db?) or better, write something like
= f.association :crew_role, label_method: :role
See simple_form documentation for more info.
I have Absolutely no idea how but "simply" dropping and re creating the database now everything works smoothly.

Radio buttons grouped over multiple nested form entries

I'm using RoR with simple_form and cocoon to handle this structure : Node has_many :addresses
Each Address has a boolean :main field which will flag one of each Node address as the main one (actually the one used for SNMP monitoring).
I've a nested form to manage these addresses but I can't find a clean way to select the main one.
Logically, I should use a radio buttons group, each address being one radio group option, but, in a nested form, each radio button will belong to one single address and have a different name, meaning they're in different groups.
I could link the radio button input to a virtual attribute in Node, and set selected address':main field to true in a callback, but how will I link each button to its address (I could use address ID as button value, but newly added addresses have no ID yet).
Here's a simplified version of what my Node form should look like (parentheses represent radio buttons, brackets for text inputs) :
Name : [MyNode1 ]
| Address | Main |
|------------|------|
| [10.0.0.1] | (*) |
| [10.0.0.2] | ( ) |
| [10.0.0.3] | ( ) |
My only other solution would be a JS feeded selectbox (options updated when I change something in addresses list) but it's definitely less clean and logical than a simple radio group...
Edit
An "acceptable" workaround would be to group radio buttons on something else than name attribute (example : data-group attribute). Does someone know any good JS/jQuery plug-in that could do this ?
I had a similar problem, with a Title that had many Role objects (a HMT glue model, which said which Person had done what to that title -- author, editor, translator, whatever). Each title had a primary person, usually the author, but sometimes an editor or curator, as in an encyclopedia. Try as I might, I could not get Rails to use a radio button in a newly created nested model, as you have seen. Here's what I ended up doing:
In the nested Role partial:
<p>
<%= f.collection_select :name, person_roles, :to_s, :titleize, {:prompt => "Role"}, {:class => 'combo'} %>
<%= f.collection_select :person_id, Person.order(:sort_name), :id, :reverse_name, :prompt => "Person" %>
<%- if ! f.object.new_record? -%>
<%= radio_button_tag 'title[role_id]', f.object.id, (#title.role_id == f.object.id) %>
<%= label_tag :role_id, "Primary", :for => "title_role_id_#{f.object.id}" %>
<%- end -%>
<%- if ! f.object.new_record? -%>
<%= f.link_to_remove "Delete", :class => 'delete' %>
<%- end -%>
In the Title model:
has_many :roles, :dependent => :destroy
has_many :people, :through => :roles
belongs_to :role #the primary person, like the author or editor
accepts_nested_attributes_for :roles, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
after_create :set_primary
def set_primary
self.update_attributes( :role_id => self.roles.first.id ) if self.roles && self.roles.first
end
So the radio buttons don't even appear unless the nested models have been saved. Adding a new nested model will give you a form partial missing the radio button and delete link (since neither would work until that nested child had been saved).

ruby on rails how to use FormOptionHelpers to create dynamic drop down list

I have checked some tutorials but I got confused by the parameters in this method
collection_select (object, attribute, collection, value_method, text_method, options = {}, html_options ={})
I have a map model includes: :area, :system, :file
and I want to read :area from database to a drop down list, and let user choose one
I already did #map = Map.all in the view
what the method should be?
especially the parameter "attribute". In a lot tutorials, people put "id" here. But I don't know what "id" is, and in my situation I don't need any other value, just the "area".
Im not exactly sure what you are asking here but if you are trying to make a dropdown selection for use in an html form will this example help you at all?
<% nations = {'United States of America' => 'USA', 'Canada' => 'Canada', 'Mexico' => 'Mexico', 'United Kingdom'=> 'UK'} %>
<% list = nations.sort %>
<%= f.select :country, list, %>
Here nations is a hash of countries then list becomes the sorted copy of that hash. An html dropdown is then created as a part of the form "f". ":country" is the part of the model that the data is connected to while list is the options to populate the dropdown with
It's not clear from your question what the model is that's being populated with the area.
Typically, collection_select is used between related models.
eg.
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :category
end
When selecting the 'category' for a product, your view would have something like:
<%= f.collection_select(:category_id, :id, Category.all, :name, include_blank: true) %>
What this does is specify the Product.category_id as the attribute being populated with the value of Category.id. The values come from the Category.all collection, and with Category.name being the item displayed in the select. The last (optional) parameter says to include a blank entry.
Something like the following is probably what you need:
<%= f.collection_select(:map_id, :id, #map, :area) %>
However, if the model you're trying to populate has an area attribute (instead of an ID linking to the map), you might need to use:
<%= f.collection_select(:area, :area, #map, :area) %>
This specifies that the area attribute of the receiving table will be populated with Map's area attribute, which is also being used as the "description" in the select.

Resources