Creating a form for a has_and_belongs_to_many relationship - ruby-on-rails

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?

Related

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.

select_check_boxes with an has many through relation and :checked option

I am using collection_check_boxes to create object in a has_many through relation;
here some models:
#emotion.rb
class Emotion < ActiveRecord::Base
has_many :emotional_states
has_many :reports, :through => :emotional_states
end
#report.rb
class Report < ActiveRecord::Base
has_many :emotional_states
has_many :emotions, :through => :emotional_states
[... other "irrelevant" stuff here ...]
end
#emotional_states.rb
class EmotionalState < ActiveRecord::Base
belongs_to :report
belongs_to :emotion
end
As you may understand when I create a Report I also select with a collection_check_box a list of Emotions I want to bind to that report (through the model EmotionalState); Everything works on create (I retrieve the hash values and if #report.save I also create EmotionalStates with the #report.id and #emotion.id.)
But when it cames to edit the Report I would like to edit also the associated EmotionalStates (this means creating new EmotionalStates or deleting old one).
How can I populate the select_check_boxes with ALL the available Emotions having checked that emotions that are alredy associated through the EmotionalStates bojects?
If I write something like:
<%= collection_check_boxes(:report, :emotion_id, #report.emotional_states.map{|e| e.emotion}, :id, :name) %>
I'll get a unchecked checkbox for every alredy associated Emotion.
<%= collection_check_boxes(:report, :emotion_id, Emotion.all, :id, :name, :checked => #report.emotional_states.map{|e| e.emotion}) %>
While this code will correctly returns Emotion.all, but will not check the emotions alredy associated to #report through #report.emotional_states.
I've searched all around the wheb for examples on the usage of :checked options for collection_select_boxes without any results...
any hint?
I did the same once in this way.you can also try :
Emotions:
<% Emotion.all.each do |emotion| %>
<%= check_box_tag 'report[emotion_ids][]' , emotion.id, #report.emotion.include?(emotion) %><%= emotion.name %><br/>
<% end %>
In Controller add :emotion_ids=>[] into strong parameters.
And one line into controller update method:
params[:report][:emotion_ids] ||= []
After coming back to this bug I discovered that the problem was an incorrect use of the .map method, mapping a whole object (e.emotion) instead its id (e.emotion.id).
This easily fixed my problem:
<%= collection_check_boxes(:report, :emotion_id, Emotion.all, :id, :name, :checked => #report.emotional_states.map{|e| e.emotion.id}) %>
Thank you for your help!

semantic_field_for for arbitrary attribute

I am quit new to rails. I have been using formtastic in my project and I find it quite easy to deal with the form objects. I have a small problem which I hope to clear out here.
I want to create a form for arbitrary objects and a has_many type of nested form for it. What I mean is semantic_form_for does not use any model instead uses symbol for creating form and this form now has to have a to_many type of semantic_fields_for. This is how my code looks,
= semantic_form_for :company do |f|
= f.inputs "company" do
= f.input :name
= f.input :enterprise_code
= f.semantic_fields_for :email do |e|
= f.inputs "email" do
= f.input :address
The form above is not associated to any model. I will pick these attributes in the controller and assign it individually. The email fields in the form has to be like has_many. Now, it is like one to one. How can this be achieved.
I used formtastic only once so i might be wrong but i don't think there's anything mentioned that you can use it only for one type of association. It only says that you for nested forms we can use semantic_fields_for :model and then both model's must be setup correctly with accepts_nested_attributes_for

can't get mongoid/rails field to accept input as array

I'm creating a Question/Answer model in a Mongoid/Rails project. I want users to create their own questions and then create possible answers: 2 answers or 3 answers or more. I've got the form so they can add as many questions as they want, but I get this error:
Field was defined as a(n) Array, but received a String with the value "d".
Not only am I not getting the array, but it's wiping out "a" "b" and "c" answers and only saving "d".
My model:
class Question
include Mongoid::Document
field :question
field :answer, :type => Array
end
The relevant section of _form.html.haml:
.field
= f.label :question
= f.text_field :question
%p Click the plus sign to add answers.
.field
= f.label :answer
= f.text_field :answer
#plusanswer
= image_tag("plusWhite.png", :alt => "plus sign")
.actions
= f.submit 'Save'
The jQuery that repeats the answer field when they need it:
$("#plusanswer").prev().clone().insertBefore("#plusanswer");
I've tried several of the solutions here involving [] in the field but getting nowhere.
Thanks much.
Two approaches:
One is to edit the form such that instead of returning back question[answer], it returns question[answer][] which will build answer to be an array.
EDIT: I'm reading your question closer and it seems like you have some JS dynamically rendering forms. In that case, setting the id with an empty bracket set [] at the end should turn the form object returned into an array
The other is to override the setter in the model to convert a string into an array. The safest way is to create a matching getter
class Question
field :answer, type: Array
def answer_as_string
answer.join(',')
end
def answer_as_string=(string)
update_attributes(answer: string.split(','))
end
end
Then use :answer_as_string in your form
If you don't want to do a lot wrangling back and forth of the values between javascript and your model and you understand how to use fields_for and nested attributes a better approach might be to make answers separate model and embed them in the question model as so:
class Question
include Mongoid::Document
embeds_many :answers
end
class Answer
include Mongoid::Document
field :content # or whatever you want to name it
end
Your form would look like this (pardon my HAML):
.field
= f.label :question
= f.text_field :question
%p Click the plus sign to add answers.
= f.fields_for :answers do |ff|
.field
= ff.label :content
= f.text_field :content
#plusanswer
= image_tag("plusWhite.png", :alt => "plus sign")
.actions
= f.submit 'Save'

Ruby/Rails: dynamically change attribute in shared partial

This should be a layup for someone...
I'm trying to change a form field's attribute depending on which controller/model is calling the partial containing the form fields...
The issue (below) is with parent_id... which references one of two columns in a dogs table. It needs to either be kennel_id or master_id depending on which view this partial is being rendered in.
Not comfortable enough, yet, with Ruby/Rails language/syntax/tools to dynamically change this without getting bogged down in if/else statements.
I'm calling a shared partial and passing in a local variable:
= render "dogs/form", :parent => #kennel
or
= render "dogs/form", :parent => #master
In the partial I'd like to:
= form_for ([parent, target.dogs.build]) do |f|
= render "shared/error_messages", :target => parent
.field
= f.label :name
= f.text_field :name
.field
= f.hidden_field :parent_id ### <= PROBLEM
.actions
= f.submit 'Save'
Just thinking out loud:
I don't know if the parent-models have the proper names for it, but you could do something like:
= f.hidden_field "#{parent.class.name.underscore}_id"
But that doesn't look right. So, why not pass it as an argument?
= render "dogs/form", :parent => #master, :foreign_key => :master_id
Or, create aliases on the dog model to handle some sort of dynamic delegation:
class Dog
def parent_id=(parent_id)
case parent.class
when Master then self.master_id = parent_id
when Kennel then self.kennel_id = parent_id
end
end
def parent_id
case parent.class
when Master then self.master_id
when Kennel then self.kennel_id
end
end
end
But that sucks too. Could the relation be polymorphic? Then you can leave out the switching.
class Dog
belongs_to :owner, :polymorphic => true
end
= f.hidden_field :owner_id
Just some ideas. Hopefully one of them makes sense to you...
Wow, my initial answer wasn't even close. I think what you'll want is a polymorphic association: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations This way, the parent can be whatever class you need it to be.

Resources