Mass edit (create/destroy) many2many relation on Rails - ruby-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

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

Rails 4 has_many through - Cannot modify association

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

Add an attribute to a joining model in Rails has_many through

I have a User model and a Book model joined with a Like model.
class Book < ActiveRecord::Base
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
class User < ActiveRecord::Base
has_many :books
# the like associations
has_many :likes
has_many :liked_books, :through => :likes, :source => :book
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :book
I want to add an attribute to the Like model so while right now a User can Like a book to add it to their profile, I want the User to be able to write a recommendation.
I generated the attribute Recommendation:text to the Like (joining) model, but am unsure how to add this to views so a User can write a recommendation that will be tied to the Like (and thus that book and user).
I'm looking at this post - Rails has_many :through Find by Extra Attributes in Join Model - which describes something similar but does not explain how to implement this in the views.
Let me know if you can point me in the right direction. Thanks!
I think you should create a separate model for this Recommendation, dependent of the Like model:
class Book < ActiveRecord::Base
belongs_to :user
has_many :comments
has_many :commenters, :through => :comments, :source => :user
class User < ActiveRecord::Base
has_many :books
has_many :comments
has_many :commented_books, :through => :comments, :source => :book
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :book
Then, the logic to create a comment for a book:
# Books Controller
def show
#book = Book.find(params[:id])
#comment = #book.comments.build
end
# show view of the book
form_for #comment do |form|
form.hidden_field :user_id, value: current_user.id
form.hidden_field :book_id
form.text_area :content # the name of the attribute for the content of the comment
form.submit "Post comment!"
end
To list all the comments of a specific User:
# users controller (profile page)
def show
#user = User.find(params[:id])
end
# show view of Users
#user.comments.includes(:book).each do |comment|
"Comment on the book '#{comment.book.name}' :"
comment.content
end
I suppose that the like functionnality works with ajax ? (remote: :true or in pure javascript)
You can append a form with the response of your request with the id of the new like, and again handle the recommendation by an ajax request

Rails 3.2 double nested form and posting from child (Mongoid)

Having trouble posting from a child to a parent's other child:
Can this be done like I want, post from the child (Order) to the Parent's (User's) other child (Customer) or do you have to go at this from some other angle?
I have 3 Models:
class User
has_many :orders
has_one :customer, dependent: :destroy
accepts_nested_attributes_for :customer
class Order
belongs_to :user
belongs_to :project
class Customer
belongs_to :user
I've tried accepts_nested_attributes_for :orders from the user, I've also tried accepts_nested_attributes_for :user from the order
neither seem to work,
Hers my form:
= form_for #order do |f|
= f.hidden_field :user_id
= f.fields_for :user do |user|
= user.fields_for :customer do |customer|
= customer.hidden_field :customer_attribute
I am not sure this is possible. Anyway you certainly forgot to add
accepts_nested_attributes_for :user
in your Order class.

Resources