Rails 4 has_many through - Cannot modify association - ruby-on-rails

I have DayItem model which has one SchoolProgram which has many seminars.
class DayItem < ActiveRecord::Base
has_one :school_program, dependent: :destroy
has_many :seminars, through: :school_program
accepts_nested_attributes_for :school_program
accepts_nested_attributes_for :seminars, reject_if: :all_blank
end
class SchoolProgram < ActiveRecord::Base
belongs_to :day_item
has_many :seminars, dependent: :destroy
accepts_nested_attributes_for :seminars, allow_destroy: true, reject_if: :all_blank
end
class Seminar < ActiveRecord::Base
belongs_to :school_program
end
I am using cocoon gem for dynamic nested forms as follows.
_form.html.haml:
= simple_form_for [#day, #day_item] do |f|
= f.input :start_time
= f.simple_fields_for :school_program do |form|
= form.input :school
= form.simple_fields_for :seminars do |seminar|
= render 'seminar_fields', :f => seminar, :parent => form
.links
= link_to_add_association 'add seminar', form, :seminars
_seminar_fields.html.haml:
.nested-fields.well.well-compact
.form-inline
= f.input :name
= link_to_remove_association "remove seminar", f
But when I try to add a seminar I get following exception.
 ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection in Etm::DayItemsController#update
Cannot modify association 'DayItem#seminars' because the source reflection class 'Seminar' is associated to 'SchoolProgram' via :has_many.
Any help is appreciated.

Circular reference in relationships (source reflection)
There may be more than one issue here, but the first one that should be addressed is that your relationship for Seminars creates a circular reference. It is declared in a has_many in DayItem and then as a has_one on SchoolProgram, which itself belongs to the parent class DayItem. Please try the change below to our DayItem model. Leave the other models as they are, and let me know how that goes.
class DayItem < ActiveRecord::Base
has_one :school_program, dependent: :destroy
accepts_nested_attributes_for :school_program
end

Related

How to update multiple checkboxes values on join table through nested attributes with Cocoon gem in Rails

I did some research and found this(Update multiple checkboxes on association model through nested attributes with Cocoon gem in Rails) but my case is a bit different i assume.
Suppose I have 3 models: User, Client, Role. The relation is many_to_many through assignments between User & Client and same many_to_many through assignments between User and Role.
class User < ApplicationRecord
has_many :assignments, inverse_of: :user, :dependent => :destroy
has_many :roles, through: :assignments
accepts_nested_attributes_for :assignments, reject_if: :all_blank, allow_destroy: true
end
class Client < ApplicationRecord
has_many :assignments, inverse_of: :client, :dependent => :destroy
has_many :users, through: :assignments
end
class Role < ApplicationRecord
has_many :assignments, inverse_of: :role, :dependent => :destroy
has_many :users, through: :assignments
end
Join table Assignment
class Assignment < ApplicationRecord
belongs_to :user
belongs_to :role
belongs_to :client
end
Assignment table contains user_id, role_id, client_id
My views:
form.html.erb
<%= f.fields_for :assignments,url: url do |assignment_form| %>
<%= render partial: 'users/assignment_fields', locals: { f: assignment_form,user: #user } %>
<% end %>
<div>
<%= link_to_add_association "Add", f, :assignments, partial: 'users/assignment_fields',render_options: { locals:{ user: #user } } %>
</div>
</div>
_assignment_fields.html.erb
<%= f.collection_check_boxes :role_id, Role.all, :id, :short_name, html_options = {} %>
params permit:
[:first_name, :last_name,assignments_attributes: [:client_id, role_id: [] ] ]
params in console.
"user"=><ActionController::Parameters {"first_name"=>"a", "last_name"=>"d", "assignments_attributes"=><ActionController::Parameters {"0"=><ActionController::Parameters {"client_id"=>"2", "role_id"=>["2", "4"], "id"=>"300"}
Now what i need to do is save the User form along with the fields shown below in the link screenshot. The below shown fields needs to be saved in the Assignments join table.Note: I am using cocoon to add more records.
I want know how can I save the role_ids that come to the Assignment which means if we have 2 roles for a client then 2 different records needs to be created in the join assignment table.
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/bAvfe.png [Screenshot link]

How to create an object if it does not exist in a nested form in Rails 6?

I need to create a Training, then add Attendances with nested form fields (cocoon) in a Rails 6 app. Here is the situation:
The models are the following:
Training model
class Training < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :people, through: :attendances
accepts_nested_attributes_for :attendances, reject_if: :all_blank, allow_destroy: true
end
Attendance model
class Attendance < ApplicationRecord
belongs_to :training
belongs_to :person
end
Person model
class Person < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :trainings, through: :attendances
end
If a Person exists in the database, then the user selects the person's name from a dropdown select field.
If the Person does not exist, then the user must enter the person's information manually by entering the name and age of the person.
How do I set up the controllers and form to allow a user to manually enter the person's name and age, and then automatically create that person and the attendance in the nested form fields when the training is saved?
The key is having an id field for the nested attributes. Rails's behavour is to update an existing record if the id is present, or to create a new record if the id is blank.
That, essentially, is all you need to do. Enter the person's name and age but leave the id empty, and a new record will be created.
I strongly recommend the cocoon gem https://github.com/nathanvda/cocoon which will give you a link_to_add_association helper for your training form that automatically lets you open a new, empty person form. When implemented you'll call the helper using something like this
<%= link_to_add_association 'add attendee', f, :people %>
You can ignore that the person is associated to the training by a join table... rails will create the join record automatically when you save the new person.
I used the link from the comment above: https://github.com/nathanvda/cocoon/wiki/A-guide-to-doing-nested-model-forms#the-look-up-or-create-belongs_to
And I used the last example in the wiki.
Here is how my final version of the MVC turned out for this feature:
The models
class Person < ApplicationRecord
has_many :attendances, dependent: :destroy
has_many :trainings, through: :attendances
end
class Attendance < ApplicationRecord
belongs_to :training
belongs_to :person
accepts_nested_attributes_for :person, :reject_if => :all_blank
end
class Training < ApplicationRecord
has_many :attendances
has_many :people, through: :attendances
accepts_nested_attributes_for :attendances, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :people
end
The trainings controller
class TrainingsController < ApplicationController
put your normal controller actions here and then in
training_params put this:
def training_params
params.require(:training).permit(
attendances_attributes: [:id, :_destroy, :person_id, person_attributes: [:id, :_destroy, :name, :age, :program_id]]
)
end
end
Add this to the trainings/_form
.row
.col.s12
.pt-5
%h6
Attendance
#people
= f.fields_for :attendances do |attendance|
= render 'attendance_fields', :f => attendance
.people_links
= link_to_add_association 'Add', f, :attendances
Add this to trainings/_attendance_fields
.nested-fields.attendance-person-fields
.field
.person_from_list
= f.select(:person_id, Person.all.map { |p| [p.name, p.id] }, { include_blank: true }, { :class => 'chosen-select' })
= link_to_add_association 'Or create', f, :person, data: {disable_with: "creating person"}
Finally in trainings/_person_fields
.nested-fields
.input-field
= f.text_field :name
= f.label :name
.input-field
= f.number_field :age
= f.label :age
I added coffee script :( in: person.coffee
$('#people').on('cocoon:before-insert', (e, attendance_to_be_added) ->
attendance_to_be_added.fadeIn 'slow'
return
)
$('#people a.add_fields').data('association-insertion-position', 'before').data 'association-insertion-node', 'this'
$('#people').on 'cocoon:after-insert', ->
$('.attendance-person-fields a.add_fields').data('association-insertion-position', 'before').data 'association-insertion-node', 'this'
$('.attendance-person-fields').on 'cocoon:after-insert', ->
$(this).children('.person_from_list').remove()
$(this).children('a.add_fields').hide()
return
return

Mass edit (create/destroy) many2many relation on Rails

I'm stucked in the way to do this task on Rails 5.
I need to "mass" edit a relation between a Project and it's Members
In the UI, I open a popup with a certain list of members (a list of User) and those member have or have not belongs to the Project (check the relations down here).
I need to have the ability to mark some of them (who not belongs to) or unmark others (who belongs to) with checkbox's and "save" the form and create/delete the relations.
For the record I have this models
// project.rb
class Project < ApplicationRecord
has_many :memberships, dependent: :destroy, class_name: 'ProjectMember'
has_many :members, through: :memberships, class_name: 'User', source: :user
end
// project_member.rb
class ProjectMember < ApplicationRecord
belongs_to :user
belongs_to :project
end
// user.rb
class User < ApplicationRecord
has_many :project_members
has_many :projects, through: :project_members
end
I'm kinda new on Rails and I'm really stucked in the way to create the forms (using SimpleForms) and how to edit the relation.
What's the correct approach? I tried to find over the web without success :(
I hope my question it's clear enough :)
I think the helpers you want are included in the gem cocoon : https://github.com/nathanvda/cocoon
Using this gem, your view for the project edition would look like this :
projects/_form
= simple_form_for #project do |f|
-# your project fields...
%h3 Members
#members
= f.simple_fields_for :members do |member|
= render 'member_fields', f: member
.links
= link_to_add_association 'add member', f, :members
= f.submit
projects/_member_fields
.nested-fields
= f.association :user
= link_to_remove_association "remove user from project", f
Edit : I focused on the view part, but before that, in the model, you have to accept nested attributes from forms :
class Project < ApplicationRecord
has_many :memberships, dependent: :destroy, class_name: 'ProjectMember'
has_many :members, through: :memberships, class_name: 'User', source: :user
accepts_nested_attributes_for :members, reject_if: :all_blank, allow_destroy: true
end
There's also a bit of work to do in the controller to permit the nested attributes.
def project_params
params.require(:project).permit(:name, :description, members_attributes: [:id, :user_id, :_destroy])
end

How to build several objects while adding association?

Rails 4.2.1, Ruby 2.2.1
Relations are:
class Region < ActiveRecord::Base
has_many :translations, dependent: :destroy
has_many :custom_properties, dependent: :destroy
has_many :languages, through: :translations
has_many :options, through: :custom_properties
accepts_nested_attributes_for :custom_properties, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :translations, reject_if: :all_blank, allow_destroy: true
end
class CustomProperty < ActiveRecord::Base
belongs_to :region
has_many :options, dependent: :destroy
has_many :custom_property_translations, dependent: :destroy
accepts_nested_attributes_for :options, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :custom_property_translations, reject_if: :all_blank, allow_destroy: true
end
class CustomPropertyTranslation < ActiveRecord::Base
belongs_to :custom_property
belongs_to :language
end
class Option < ActiveRecord::Base
belongs_to :custom_property
has_many :option_translations, dependent: :destroy
accepts_nested_attributes_for :option_translations, reject_if: :all_blank, allow_destroy: true
end
class OptionTranslation < ActiveRecord::Base
belongs_to :option
belongs_to :language
end
In region form I'm using cocoon for nested fields.
= f.simple_fields_for :custom_properties do |custom_property|
= render 'custom_property_fields', f: custom_property
.links
= link_to_add_association 'add property', f, :custom_properties
And nested form for CustomPropertyTranslation and OptionTranslation.
= f.simple_fields_for :custom_property_translations do |custom_property_translation|
= render 'custom_property_translation_fields', f: custom_property_translation
.links
= link_to_add_association t('.add_translation'), f, :custom_property_translations
I wan't to automatically build several CustomPropertyTranslation and OptionTranslation depending on how many languages are the region has.
I tried to use after_initialize callback to build necessary associations but it worked only for existing custom properties. How do I build several associations at once on click add translation ?
you can use the count key in html_options of link_to_add_association helper to determine how many new objects you want to create
= f.simple_fields_for :custom_property_translations do |custom_property_translation|
= render 'custom_property_translation_fields', f: custom_property_translation
.links
= link_to_add_association t('.add_translation'), f, :custom_property_translations, {count: 3}
More on the available options here: https://github.com/nathanvda/cocoon/blob/be59abd99027b0cce25dc4246c86d60b51c5e6f2/lib/cocoon/view_helpers.rb

Searching one model by another

My application consists of a drink model
class Drink < ActiveRecord::Base
attr_accessible :name
has_many :recipe_steps, :dependent => :destroy
has_many :ingredients, through: :recipe_steps
end
An ingredient model
class Ingredient < ActiveRecord::Base
attr_accessible :name
has_many :recipe_steps
end
how would I go about having it so when a user searches an ingredient that it returns all of the drinks with that ingredient?
Additional information: I'm currently using sunspot/solr for my searching.
First, in your Ingredient model you'd need this line:
has_many :drinks, through: :recipe_steps
To define the has_many, through: relationship. Make sure that RecipeStep has these lines, too:
belongs_to :ingredient
belongs_to :drink
Then you can do something like in the DrinksController:
def search
term = params[:search]
ingredient = Ingredient.where(:name => term)
#drinks = Ingredient.find(ingredient).drinks
end
And your form should look something like this:
<%= form_for #drink, :url => { :action => "search" } do |f| %>
<%= f.text_field :search %>
<% end %>
I don't know all your names for everything but this should get you going.
Following should work fine:
class Ingredient < ActiveRecord::Base
...
has_many :recipe_steps
has_many :drinks, through: :recipe_steps
end

Resources