Rails 5 how to edit join model attributes from the parent model's view? - ruby-on-rails-5

I have a system where admins can create Exams and price them according to the Branch they are selling them in. So for example, Exam One can cost $5 in Branch One, but $10 in Branch Two.
I made a join table named ExamOffering that has the price of the exam, so each Exam can be sold at many Branches at different prices, and each Branch can have many Exams. Like this:
class Branch < ApplicationRecord
has_many :exam_offerings
has_many :exams, through: :exam_offerings
end
class Exam < ApplicationRecord
has_many :exam_offerings
has_many :branches, through: :exam_offerings
end
class ExamOffering < ApplicationRecord
# this class has a 'price' attribute
belongs_to :exam
belongs_to :branch
end
I need to be able to create a new Exam and select a Branch in the form in order to put in a price, but that attribute is not part of the Exam model, but the ExamOffering join table. I've tried some ways but failed (using accepts_nested_attributes_for :exam_offerings in the Exam model or iterating through all the Branches and creating ExamOfferings for each one in the controller). What's the 'Rails way' of doing this? I think it's a pretty common case, but I haven't found an answer that fits my case. Maybe this has a name and I don't know it.
It could be phrased like this: When I'm creating a new Exam, I want to be able to input a price for each existing Branch.
Thanks.

What you want to do is create the join model explicitly:
# config/routes.rb
resources :exam_offerings, only: [:new, :create]
class ExamOffering < ApplicationRecord
# this class has a 'price' attribute
belongs_to :exam
belongs_to :branch
accepts_nested_attributes_for :exam
validates_associated :exam
end
# app/views/exam_offerings/new.html.erb
<%= form_with(model: #exam_offering) do |f| %>
<div class="field" %>
<%= f.label(:branch_id) %>
<%= f.collection_select(:branch_id, Branch.all, :id, :name) %>
</div>
<div class="field" %>
<%= f.label(:price) %>
<%= f.number_field(:price) %>
</div>
<%= f.fields_for(:exam) do |exam_field| %>
<div class="field" %>
<%= exam_field.label(:foo) %>
<%= exam_field.text_field(:foo) %>
</div>
<% end %>
<%= f.submit %>
<% end %>
class ExamOfferingsController < ApplicationController
# GET /exam_offerings/new
def new
#exam_offering = ExamOffering.new
end
# POST /exam_offerings
def create
#exam_offering = ExamOffering.new(exam_offering_params)
if #exam_offering.save
redirect_to #exam_offering, notice: 'Offering Created'
else
render :new, notice: 'Oh No!'
end
end
private
def exam_offering_params
params.require(:exam_offering)
.permit(
:branch_id, :price,
exam_attributes: [:foo]
)
end
end
Remember that there is nothing special about join models - and they don't always have to be created implicitly. Quite often they are actually an important part of the buisness logic and not just plumbing.
But really if I was building this I would just create a normal POST /exams route where the user can create an exam and then let them create the offering after that. Use AJAX if needed to make it appear seamless.

I ended up using accepts_nested_attributes_for in the Exam model, so I didn't have to use another view to put the price, so it's something like this:
class Branch < ApplicationRecord
has_many :exam_offerings
has_many :exams, through: :exam_offerings
end
class Exam < ApplicationRecord
has_many :exam_offerings
has_many :branches, through: :exam_offerings
accepts_nested_attributes_for :exam_offerings
end
class ExamOffering < ApplicationRecord
# this class has a 'price' attribute
belongs_to :exam
belongs_to :branch
end
The important part was the view. I needed to use fields_for with a new object when creating the exam, but fetch the existing exam_offerings when editing the exam, so I have this code:
<% if exam.exam_offerings.empty? %>
<% Branch.all.each do |branch| %>
<%= form.fields_for :exam_offerings, #exam.exam_offerings.build do |offering| %>
<div class="field">
<%= offering.label "Price in #{branch.name}" %>
<%= offering.text_field :price %>
<%= offering.hidden_field :branch_id, value: branch.id %>
</div>
<% end %>
<% end %>
<% else %>
<%= form.fields_for :exam_offerings do |offering| %>
<div class="field">
<%= offering.label "Price in #{offering.object.branch.name}" %>
<%= offering.text_field :price %>
</div>
<% end %>
<% end %>

Related

Is there a more efficient way than this to load records associated with records in a list?

I have a model Playlist, and a model User, both of which have_many of each other, through a join model PlaylistUser.
On my playlists#show action, I want to print a list of all of a Playlist's Users, along with the first two Playlists associated with each of those Users.
Right now here's what I have:
playlists/show.html.erb
<% #playlist = Playlist.find(params[:id]) %>
<% #playlist.users.each do |user| %>
<%= user.name %>
<%= user.playlists.first.name %>
<%= user.playlists.second.name %>
<% end %>
models
class User < ActiveRecord::Base
has_many :playlist_users
has_many :playlists, :through => :playlist_users
end
class PlaylistUser < ActiveRecord::Base
belongs_to :playlist
belongs_to :user
end
class Playlist < ActiveRecord::Base
has_many :playlist_users
has_many :users, :through => :playlist_users
end
But there is an enormous change in performance when I delete the user.playlists lines and print out only the user.name, because then the database only has to make one query, as opposed to hundreds.
Does anyone know of a way to make this more efficient? Maybe I could somehow load all the associated Playlists in the original query?
You can use the includes method to tell Rails to preload associated records with just one query upfront.
Loading from the database is a controller responsibility and should not happen in the view. Therefore, add the following to your controller:
playlist = Playlist.find(params[:id])
#users = playlist.users.includes(:playlists)
And change your view to just iterate over the users array:
<% #users.each do |user| %>
<%= user.name %>
<%= user.playlists.first.name %>
<%= user.playlists.second.name %>
<% end %>

Can't mass assign protected attributes in has_many belongs_to association

Am using rails 3.2.13 and I have models for two entities like so
class Restaurant < ActiveRecord::Base
attr_accessible :description, :menu, :restaurant_name
has_many :cuisines
end
class Cuisine < ActiveRecord::Base
attr_accessible :cuisine_name, :restaurant_id
attr_accessible :cuisine_ids
belongs_to :restaurant
end
The controller action for creating a restaurant look like this
I have a form for creating a restaurant using simple form gem like so
<%= simple_form_for #restaurant do |f| %>
<%= f.input :restaurant_name %>
<%= f.input :description %>
<%= f.input :menu %>
<%= f.association :cuisines, label_method: :cuisine_name %>
<%= f.button :submit %>
<% end %>
Am basically suppose to chose from a group of cuisines which simple form helps with. However when i select the cuisine and try to create the restaurant. It brings back the error.
ActiveModel::MassAssignmentSecurity::Error at /restaurants
Can't mass-assign protected attributes: cuisine_ids
As you can see in the model. I placed attribute as accessible but it didn't work. I even tried the singular version cuisine_id with no luck. I have no idea what is wrong? I would prefer not to tamper with the rails defaults for protecting against mass assignment. Any clues?
Cuisine doesn't have cuisine_ids, Restaurant does.
Move your attr_accessible :cuisine_ids into the Restaurant model.

Mass assignment error in nested form in Rails

transaction.rb model:
class Transaction < ActiveRecord::Base
attr_accessible :customer, :tickets_attributes
has_many :tickets
accepts_nested_attributes_for :tickets
end
ticket.rb model:
class Ticket < ActiveRecord::Base
attr_accessible :booking_id, :quantity, :transaction_id
belongs_to :transaction
belongs_to :booking
end
in the view page i have a nested rails form for multiple entries of ticket:
<%= form_for(#transaction) do |f| %>
<%= f.text_field :customer %>
<% #sezzion.bookings.each do |booking| %>
<%= booking.bookingdate %>:
<%= f.fields_for :ticket do |t| %>
<%= t.text_field :quantity, :value => 0, :class => "quantity" %>
<%= t.hidden_field :booking_id, :value => booking.id %>
<% end %>
<% end %>
<%= f.submit "create transaction" %>
<% end %>
When I'm submitting the form, I have the following error:
ActiveModel::MassAssignmentSecurity::Error in TransactionsController#create
Can't mass-assign protected attributes: ticket
I have attr_accessible :tickets_attributes and accepts_nested_attributes_for :tickets in the transaction model and there's still an error. Also when I add plural to the ticket on line <%= f.fields_for :ticket do |t| %>, the quantity field doesn't display.
Your form f is based off of a Transaction object, which has_many :tickets. I believe you should be using the plural :tickets rather than the singular :ticket in your fields_for.
<%= f.fields_for :tickets do |t| %>
If you always want a new ticket, you may need to do:
<%= f.fields_for :tickets, Ticket.new do |t| %>
to ensure that a create form shows up.
total re-edit -- sorry its been a while I had to refresh my memory
transaction.rb tickets_attributes is ok.
class Transaction < ActiveRecord::Base
attr_accessible :customer, :tickets_attributes
has_many :tickets
accepts_nested_attributes_for :tickets
end
transaction_controller.rb you must build the tickets.
def new
#transaction = Transaction.new
#transaction.tickets.build
end
new.rb or in your form, fields_for must be for :tickets as rob as pointed out:
<%= form_for(#transaction) do |f| %>
...
<%= f.fields_for :tickets do |t| %>
...
I think you might be missing the build part in the controller. hope that helps!

HABTM Checkboxes using simpleform gem

I have three models, and i am trying to save onto a third table using simple form gem and checkboxes.
class Work < ActiveRecord::Base
has_many :skills, through: :skillships
end
The second model is
class Skill < ActiveRecord::Base
has_many :works, through: :skillships
end
The third is
class Skillship < ActiveRecord::Base
belongs_to :work
belongs_to :skill
end
Using the Work model i am trying to save the data on the skillship table. Something similar to this http://railscasts.com/episodes/17-habtm-checkboxes-revised. Can you please help.
EDIT
The form
<%= simple_form_for(#work) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :title, :label => 'Project Title' %>
<%= f.input :excerpt, :as => :text %>
<fieldset>
<legend>Skills Used </legend>
Would like to check the skills i used here.
</fieldset>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
I tried..
<%= hidden_field_tag "work[skill_ids][]", nil %>
<% Skill.all.each do |skill| %>
<%= check_box_tag "work[skill_ids][]", skill.id, #work.skill_ids.include?(skill.id), id: dom_id(skill) %>
<%= label_tag dom_id(skill), skill.title %><br>
<% end %>
The reason i'm doing this it because work can have many skills used.
I was going about this the wrong way. A join table solved the problem.
rails g migration create_skills_works_table
Then
class CreateSkillsWorksTable < ActiveRecord::Migration
def self.up
create_table :skills_works, :id => false do |t|
t.references :skill
t.references :work
end
add_index :skills_works, [:skill_id, :work_id]
add_index :skills_works, [:work_id, :skill_id]
end
def self.down
drop_table :skills_works
end
end
Using simple form on the work view.
<fieldset>
<legend>Skills Used </legend>
<%= f.association :skills %>
</fieldset>

How to create related models within a form in rails

Newbie question here.
I have two models which are related to each other:
class Relationship < ActiveRecord::Base
...
attr_accessible :source_item_id, :target_item_id
belongs_to :target_item, :class_name => "Item"
belongs_to :source_item, :class_name => "Item"
belongs_to :user
...
end
and:
class Item < ActiveRecord::Base
...
attr_accessible :address
...
end
Now, within my form, I already know the source_item_id. I want to be able to enter an address into the form, and create both a target_item, and the associated Relationship.
<%= form_for #new_relationship do |f| %>
<% #new_relationship.source_item_id = #current_item.id %>
<%= f.hidden_field :source_item_id %>
<%= f.submit "New Relationship" %>
<% end %>
You generally do the relationship in the controller and let the form just collect the data. If you are asking how to have a form with two models, check out this post here. I hope I understood your question right !!!