HABTM relationship with a text/hidden field for ids - ruby-on-rails

I'm creating a has_and_belongs_to_many relationship with Rails. Each group has many participants and each participant can be part of many groups.
The relationship seems to be set up ok as I can use check boxes to add relationships using this in my form:
<%= collection_check_boxes(:group, :participant_ids, #participants, :id, :name) %>
However, I need to use a hidden field to submit these relationships (I use AJAX to fetch them in the view) with an array of ids (e.g. [1, 3]). I've tried using a text field like this but it doesn't save the data:
<%= f.text_field :participant_ids %>
When participant_ids saves using the checkboxes and I output it on the show view it's an array of ids but I can't seem to submit it in that format to start with.
Why can't I submit the participant_ids using a text/hidden field and is there a way around this?
For reference I've set up the join table and the models look like this:
class Group < ActiveRecord::Base
has_and_belongs_to_many :participants
end
class Participant < ActiveRecord::Base
has_and_belongs_to_many :groups
end
I also modified the group controller to work with strong parameters like this:
def group_params
params.require(:group).permit(:user_id, :purpose, :participant_ids => [])
end
I can post more code if necessary.

This answer worked for me. You will have to
<% #participants.each do |participant| %>
<% f.hidden_field 'participant_ids][', :value => participant.id %>
<% end %>

Related

Rails multiple separate dropdown fields for has_and_belongs_to_many association

I have a two models associated with has_and_belongs_to_many
# post.rb
class Post < ApplicationRecord
has_and_belongs_to_many :keywords
I know that in views/posts/_form.html.erb I could do something like
form.select(:keyword_ids, Keyword.all.collect {|k| [ k.name, p.id ] }, multiple: true)
So then I would get one multi-select dropdown.
What if I want to have each keyword_id in a separate drop-down menu (not combined in one) and have all the keyword_ids from the separate dropdowns collected in one array when the form is submitted?
I tried solving it with fields_for and accepts_attributes_for but in that case, when updating the model the id that's sent from the form to the controller is always fixed to the ones of the keywords that the model had initially and it seems like it cannot be changed which makes sense since I understand this function is only supposed to change attributes of children, not change the child itself.
In the view
# views/posts/_form.html/erb
<% #post.keywords.each do |keyword| %>
<%= select_tag "post[keyword_ids][]", options_from_collection_for_select(Keyword.all, "id", "name", keyword.id) %>
<% end %>
Adding keyword.id in the options_from_collection_for_select will set the default value to the existing value if the model is updated.
In the controller
# post_controller.rb
def post_params
params.require(:post).permit({ keyword_ids: [] })
end

why is my form not generating select options?

I'm trying to build a simple filter on my raffles index page to filter my raffles by category. However, none of the select options are populating- the drop down is completely blank. I think it's something to do with my view, I'm admittedly no expert with forms.
#app/controllers/raffles_controller.rb
def index
#raffles = Raffle.filter(params[:filter])
end
#app/models/raffle.rb
class Raffle < ApplicationRecord
has_many :tickets
has_many :users, through: :tickets
def self.filter(filter)
if filter
raffle = Raffle.where(category: filter)
else
Raffle.all
end
end
end
#app/views/raffles/index.rb
<h1>Current Raffles:</h1>
<%= form_tag(raffles_path, method: 'get') do %>
<%= select_tag(Raffle.pluck(:category).uniq, params[:filter]) %>
<%= submit_tag ("Filter") %>
<% end %>
any ideas?
Your call to select_tag is missing the name on the first parameter, and your list of options should be built as well.
To build the property list you can use the helper options_for_select
If you want to define a selected value, you can send it to options_for_select as well.
Based on your example, this should do the job:
<%= select_tag(:filter, options_for_select(Raffle.pluck(:category).uniq, params[:filter])) %>
A good source when you have questions about how a certain form helper works are https://guides.rubyonrails.org/form_helpers.html#the-select-and-option-tags

How to create multiple records with a nested form using check boxes

I have a Pizza model and Topping model with a PizzaTopping join table. Pizza has many toppings and topping belongs to pizza. I'm a novice with Rails.
My problem is trying to understand how to created a nested form that will add multiple records into my PizzaTopping join table. I also need the toppings to show up in check box form.
<div class="form-horizontal">
<%= form_for(#pizza) do |f| %>
Confusion #1:
To my understand this is to show a full model in check boxes, which works, but I'm confused on how the controller accepts this and creates the records in the join table of the toppings associated with the pizza. I want separate records(and not an attribute that is an array of topping ids):
PizzaTopping.create(id: 1, pizza_id: 1, topping_id: 1)
PizzaTopping.create(id: 2, pizza_id: 1, topping_id: 2)
PizzaTopping.create(id: 3, pizza_id: 1, topping_id: 3)
..
<div class="form-group">
<%= f.collection_check_boxes(:topping_ids, Topping.all, :id, :name) do |b| %>
<%= b.check_box %>
<%= b.label %>
<% end %>
</div>
or Confusion # 2:
This is a nested form but how do I get the toppings in check boxes from the Topping model and same as above, how do I code this in my controller to add records in the join table.
<div class="form-group">
<%= f.fields_for :toppings do |builder| %>
<%= builder.check_box %> // confused what I would even do next
<% end %>
</div>
...
<%= f.submit %>
<% end %>
</div>
First lets setup a indirect many to many relationship though the pizza_toppings table.
class Pizza < ActiveRecord::Base
has_many :pizza_toppings
has_many :toppings, through: :pizza_toppings
end
class Topping < ActiveRecord::Base
has_many :pizza_toppings
has_many :pizzas, through: :pizza_toppings
end
class PizzaTopping < ActiveRecord::Base
belongs_to :pizza
belongs_to :topping
end
What this accomplishes is that it lets you associate any number of pizzas with any number of toppings and ActiveRecord will handle joining for you:
#pizza = Pizza.find_by(name: 'Vesuvio')
#pizza.toppings
# => Topping( name: cheese ) ...
#pizza.toppings << Topping.find_by(name: 'Ham')
# inserts a record into the pizza_toppings table
# you can also do the inverse
#topping = Topping.find_by(name: 'Anchovies')
#topping.pizzas
# => Pizza( name: 'Napoli' )
To setup one to any or many to many relations via a checkbox you can use the collection_check_boxes helper.
<% form_for(#pizza) do |f| %>
<% f.collection_check_boxes(:topping_ids, Topping.all, :id, :name) %>
<% end %>
When you give a model a has_many association it gets a _ids setter which takes an array of ids and adds / removes associations, in this case ActiveRecord is also smart enough to know that it should setup the association through the join table when you use the through option.
The checkboxes generated by collection_check_boxes give you just that - an array in the params containing the ids of the selected toppings.
Note that you don't need to use fields_for here unless you intend to let users create pizzas and toppings on the same page. Also make sure you whitelist the topping_ids param.
def pizza_params
params.require(:pizza).permit(:name, topping_ids: [])
end
And now you got me all hungry.
First things first, if you are using a join table then you need to organise your relations differently, it makes no sense to use a join table with a belongs_to relationship, what you want to do is organise your relations so that a Pizza has_many :toppings, and a Topping has_many :pizzas and use the through: :pizza_toppings key.
Now onto your first confusion,
If you are using checkboxes the most you can hope to receive is the value of that checkbox, most likely the ids, this is then down to you to instantiate the records once you have the array of ids, perhaps something along the lines of selected_toppings = Topping.where(id: params[:topping_ids]) which will instantiate a collection of the toppings that were selected.
Also if you have your relations set up properly there is no need to explicitly create PizzaTopping records, this is a join table, and in my opinion I prefer to handle everything using the relations, something along the lines of pizza.toppings << selected_toppings should do the trick to set it all up.
Confusion two:
I dont think what you are looking for is a nested form, because you are not actually creating toppings, you just want a list of the toppings to relate to a pizza. Nested forms are when you want to create/edit and then write the attributes of a relation, your toppings are already preset, so just simply make checkboxes in the form normally with the values of your toppings ids.
Hope this helps!

simple_form rails 4, set automatic association

is a little project and I try to associate patient model with consultations. one patient has_many :consultations, in my form I have:
<%= f.association :patient %>
I pass the id parameter from the patient to the action 'new' in this way:
<%= link_to new_consultum_path(:id => #patient.id) %>
And in the view I have:
How can I make that the f.association field take the correspondent patient_id automatically?
How can I be sure that the patient_id is the current patient?
If I want to hide this field is that ok if I put
instead of
Is a better way to do this?
And why in the view shows me # patient:0x007f4e7c32cbd0 ?
thanks for your help.
And why in the view shows me # patient:0x007f4e7c32cbd0
This is a Patient object.
It means you need to call an attribute of this object - EG #patient.name.
--
f.association field take the correspondent patient_id automatically
This might help:
It looks like Organization model doesn't have any of these fields: [
:to_label, :name, :title, :to_s ] so SimpleForm can't detect a default
label and value methods for collection. I think you should pass it
manually.
#app/models/patient.rb
class Patient < ActiveRecord::Base
def to_label
"#{name}"
end
end
Apparently, you need to have either title, name or to_label methods in your model in order for f.association to populate the data.
-
How can I be sure that the patient_id is the current patient?
If you're having to verify this, it suggests inconsistencies with your code's structure. If you need the patient_id to be set as the current patient, surely you could set it in the controller:
#app/controllers/consultations_controller.rb
class ConultationsController < ApplicationController
def create
#consultation = Constultation.new
#consultation.patient = current_patient
#consultation.save
end
end
I can provide more context if required.
You want to associate consultations to patients using fields_for, which is similar to form_for, but does not build the form tags.
It you start with your patient object, you can iterate through the consultation associations binding it to form fields as you go.
it would look something like this
<%= form_for #patient do |patient_form| %>
<% patient_form.text_field :any_attribute_on_patient %>
<% #patient.consultations.each do |consultation| %>
<%= patient_form.fields_for consultation do |consultation_fields| %>
<% consultation_fields.text_field :any_attribute_on_consulatation %>
<% end %>
<% end %>
<% end %>
Sorry, the code may not be exactly right.
Check out the docs for field_for here
You will also have to set accepts_nested_attributes_for consultations on patient. When you set accepts_nested_forms_for, Rails will automatically update the associated consultations object to the patient and save any edits you have made. You DEFINITELY want to use accepts_nested_attributes_for most nested form handling of this type.

Ruby on Rails -- Saving and updating an attribute in a join table with has many => through

To simplify things, I have 3 tables :
Person
has_many :abilities, through => :stats
Ability
has_many :people, through => :stats
Stats
belongs_to :people
belongs_to :abilities
Stats has an extra attribute called 'rating'.
What I'd like to do is make an edit person form that always lists all the abilities currently in the database, and lets me assign each one a rating.
For the life of me, I can't figure out how to do this. I managed to get it to work when creating a new user with something like this:
(from the people controller)
def new
#character = Character.new
#abilities = Ability.all
#abilities.each do |ability|
#person.stats.build(:ability_id => ability.id )
end
end
From the people form:
<% for #ability in #abilities do %>
<%= fields_for "person[stats_attributes]" do |t| %>
<div class="field">
<%= t.label #ability.name %>
<%= t.hidden_field :ability_id, :value => #ability.id, :index => nil %>
<%= t.text_field :rating, :index => nil %>
</div>
<% end %>
<% end %>
This successfully gives me a list of abilities with ratings boxes next to them, and lets me save them if i'm making a new user.
The problem is that if I then load up the edit form (using the same form partial), it doesn't bring back the ratings, and if I save, even with the exact same ratings, it creates duplicate entries in the stats table, instead of updating it.
I realize I'm a terrible programmer and I'm probably doing this the wrong way, but how do I get the edit form to recall the current ratings assigned to each ability for that user, and secondly how do i get it to update the rating instead of duplicating it if the combination of person and ability already exists?
Shouldn't that be
Character
has_many :stats
has_many :abilities, through => :stats
Ability
has_many :stats
has_many :characters, through => :stats
Stat
belongs_to :character
belongs_to :ability
?
Also, is it Person or Character? You refer variously to both. (I'm going to go with Character in my answer)
I think you've fallen foul of the "I'll try to make a simplified version of my schema in order to attempt to illustrate a problem but instead make things more complex and muddle the issue by screwing it up so it doesn't make sense" syndrome. Anyway, there's a couple of issues i can see:
1) first thing is that you're adding all the possible abilities to a character as soon as they're created. This is silly - they should start out with no abilities by default and then you create join table records (stats) for the ones they do have (by ticking checkboxes in the form).
2) A simple way to manipulate join records like this is to leverage the "ability_ids=" method that the has_many :abilities macro gives you - referred to as "collection_ids=" in the api http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001885&name=has_many
In other words, if you say
#character.ability_ids = [1,12,30]
then that will make joins between that character and abilities 1, 12 and 30 and delete any other joins between that character and abilities not in the above list. Combine this with the fact that form field names ending in [] put their values into an array, and you can do the following:
#controller
def new
#character = Character.new
#abilities = Ability.all
end
#form
<% #abilities.each do |ability| %>
<div class="field">
<%= t.label #ability.name %>
<%= check_box_tag "character[ability_ids][]" %>
</div>
<% end %>
#subsequent controller (create action)
#character = Character.new(params[:character]) #totally standard code
Notice that there's no mention of stats here at all. We specify the associations we want between characters and abilities and let rails handle the joins.
Railscasts episodes 196 and 197 show how to edit several models in one form. Example shown there looks similar to what you're trying to do so it might help you out (same episodes on ascicasts: 196, 197).

Resources