Simple Form check box for join table relationship - ruby-on-rails

I can't figure this out for the life of me but here are my models:
class User < ApplicationRecord
has_many :user_stores
has_many :stores, through: :user_stores
end
class UserStore < ApplicationRecord
belongs_to :user
belongs_to :store
end
class Store < ApplicationRecord
has_many :user_stores
has_many :users, through: :user_stores
end
So I have a join table, I'm trying to make a form, which would have selected checkboxes next to the store names that the user has selected (this information would come from the join table relationship) and open checkboxes for the remaining stores (coming from the Store model). How do I show that in the view/make it work in the controller as well. Would I use collections instead? ( I am using Devise and Simple Form gem )
This is what I have so far:
<h1>Add Favorite Stores</h1>
<%= simple_form_for(#user, html: { class: 'form-horizontal' }) do |f| %>
<%= f.fields_for :stores, #user.stores do |s| %>
# not sure if this is the right way or not
<% end %>
<%= f.button :submit %>
<% end %>
Store Controller:
class StoresController < ApplicationController
...
def new
#user = current_user
#stores = Store.all
# #user.stores => shows user's stores (from join table)
end
end

When you set up a one or many to many relationship in rails the model gets a _ids setter:
User.find(1).store_ids = [1,2,3]
This would for example setup a relation between user 1 and the stores with ids 1,2 and 3.
The built in Rails collection form helpers make use of this:
<%= form_for(#user) do |f| %>
<% f.collection_check_boxes(:store_ids, Store.all, :id, :name) %>
<% end %>
This creates a list of checkboxes for each store - if an association exists it will already be checked. Note that we are not using fields_for since it is not a nested input.
SimpleForm has association helpers which add even more sugar.
<h1>Add Favorite Stores</h1>
<%= simple_form_for(#user, html: { class: 'form-horizontal' }) do |f| %>
<%= f.association :stores, as: :check_boxes %>
<%= f.button :submit %>
<% end %>

Related

Rails has_many :through nested forms with simple form

I am trying to make a player character generator. I have a form that hopefully will allow me to attach skills with their values to a character sheet model. I made models like this:
class CharacterSheet < ApplicationRecord
has_many :character_sheet_skills, dependent: :destroy
has_many :skills, through: :character_sheet_skills
belongs_to :user
accepts_nested_attributes_for :skills
end
class Skill < ApplicationRecord
has_many :character_sheet_skills, dependent: :destroy
has_many :character_sheets, through: :character_sheet_skills
attr_reader :value
end
class CharacterSheetSkill < ApplicationRecord
belongs_to :skill
belongs_to :character_sheet
end
Character sheet model holds data about player character and skill model has all skills available in game. In CharacterSheetSkill I'd like to store the skills that the player chooses for his character together with an integer field setting the skill value.
When opening form, I already have a full list of skills in database. All I want to do in form is create a character sheet that has all of these skills with added value. I tried using "fields_for" in form, but I couldn't really get that to work. Right now it looks like this:
<%= simple_form_for [#user, #sheet] do |f| %>
<%= f.input :name %>
<%= f.input :experience, readonly: true, input_html: {'data-target': 'new-character-sheet.exp', class: 'bg-transparent'} %>
...
<%= f.simple_fields_for :skills do |s| %>
<%= s.input :name %>
<%= s.input :value %>
<% end %>
<% end %>
How can I make that form so it saves character sheet together with CharacterSheetSkills?
A better idea here is to use skills as a normalization table where you store the "master" definition of a skill such as the name and the description.
class CharacterSheetSkill < ApplicationRecord
belongs_to :skill
belongs_to :character_sheet
delegate :name, to: :skill
end
You then use fields_for :character_sheet_skills to create rows on the join table explicitly:
<%= f.fields_for :character_sheet_skills do |cs| %>
<fieldset>
<legend><%= cs.name %></legend>
<div class="field">
<%= cs.label :value %>
<%= cs.number_field :value %>
</div>
<%= cs.hidden_field :skill_id %>
</fieldset>
<% end %>
Instead of a hidden fields you could use a select if you want let the user select the skills.
Of course nothing will show up unless you "seed" the inputs:
class CharacterSheetController < ApplicationController
def new
#character_sheet = CharacterSheet.new do |cs|
# this seeds the association so that the fields appear
Skill.all.each do |skill|
cs.character_sheet_skills.new(skill: skill)
end
end
end
def create
#character_sheet = CharacterSheet.new(character_sheet_params)
if #character_sheet.save
redirect_to #character_sheet
else
render :new
end
end
private
def character_sheet_params
params.require(:character_sheet)
.permit(
:foo, :bar, :baz,
character_sheet_skill_attributes: [:skill_id, :value]
)
end
end

Rails: Dynamically create form fields, create multiple records with one submit

My website lets users book a car. There are many cars to choose from, cars have different max_occupancy numbers. Once the user selects a car the sign up process starts, the user has to submit a name for every passenger.
Models
class Booking < ActiveRecord::Base
has_many :cars
accepts_nested_attributes_for :cars
end
class Car < ActiveRecord::Base
belongs_to :bookings
has_many :passengers
accepts_nested_attributes_for :passengers
end
class Passenger < ActiveRecord::Base
belongs_to :cars
accepts_nested_attributes_for :passengers
end
Controllers
def new
#booking = Booking.new
#car = #booking.cars.build
#car.passengers.build
end
def signup
#car = Car.find(params[:id])
#max_occupancy = #car.max_occupancy
#booking = Booking.new
end
Form
<%= form_for(#booking) do |f| %>
<%= f.fields_for :cars do |car| %>
<%= car.hidden_field :car_id, value: #car.id %>
<%= car.number_field :quantity, value: 1, class: 'form-control', min: 1 %>
<%= car.fields_for :passengers do |passenger| %>
<%= passenger.label :name %>
<%= passenger.text_field :name %>
<% end %>
<% end %>
<% end %>
Functionality
When the form first renders the default quantity is set to 1, so on page load I would like the block
<%= passenger.label :name %>
<%= passenger.text_field :name %>
to render 1 * #car.max_occupancy. If quantity is increased or decreased, the block should get rendered n * #car.max_occupancy (where n is quantity as set in the number_field). Any blank passenger fields should be disregarded
How would I go about creating this functionality and how would I need to change the controller to create multiple passengers with a single submit.

Create 2 types of resources using the same view

I have 2 models (and resources) - Institute and Admin.
I want to have a view with 1 submit button that creates 2 types of resources. Would I need to have 2 separate forms? An example would be great!
Also, what naming convention should this view use (given that it creates 2 types resources).
There is a "has-many through" association between Institute and Admin.
What you want is a design pattern called Form Object.
https://robots.thoughtbot.com/activemodel-form-objects
With a Form Object, you can create a class that represents the form, validate the data and then persist to the resource (or resources) that you need.
There's also a gem called Virtus for that. For me, it's a overkill if what you want is simple. You could just create a ActiveModel model and do your stuff.
Would I need to have 2 separate forms?
Answer is Non. you can make one form nested.
So example : Gessing your "has many through" association like this: One institue has many admins throuth mettings
Models :
class Institute < ActiveRecord::Base
has_many :mettings
has_many :admins, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Admin < ActiveRecord::Base
has_many :mettings
has_many :institues, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Metting < ActiveRecord::Base
belongs_to :institue
belongs_to :admin
accepts_nested_attributes_for :institues
end
Controller :
def new
#institue= Institue.new
#metting= #institue.mettings.build
#admin = #metting.build_admin
end
def create
Institue.new(institue_params)
end
def institue_params
params.require(:institue).permit(:id, mettings_attributes: [:id, :metting_time, admin_attributes: [:id ] )
end
Views can be called _form.erb.rb included in edit.erb.rb:
<% form_for(#institue) do |institue_form| %>
<%= institue_form.error_messages %>
<p>
<%= institue_form.label :name, "Institue Name" %>
<%= institue_form.text_field :name %>
</p>
<% institue_form.fields_for :mettings do |metting_form| %>
<p>
<%= metting_form.label :metting_date, "Metting Date" %>
<%= metting_form.date_field :metting_date %>
</p>
<% metting_form.fields_for :admin do |admin_form| %>
<p>
<%= admin_form.label :name, "Admin Name" %>
<%= admin_form.text_field :name %>
</p>
<% end %>
<% end %>
<p>
<%= institue_form.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', institues_path %>

Rails: How do I submit multiple objects into strong params?

I am making a goal tracking app. Right now outcome, purpose, action, priority, resources, and direction are all things which are part of Outcome in the database. However, I want to make purpose and action their own model objects. What I am confused about is how do I submit Outcome, Purpose, and Action, which will be 3 separate model objects, in a single HTTP request?
Should I just use multiple strong params in my controller?
app/view/outcomes/new.html.erb
You need to have model associations of outcomes with purpose and action.
Then you will need to create nested form. So that outform form can wrap purpose and action model attributes.
As you want to have different models for actions and purposes, I'm assuming outcome can has_many purposes and has_many actions. As per this type of association, below is the code you should have.
Your form will become something like:
<%= form_for #outcome do |f| %>
<%= f.label :outcome, "Outcome" %>
<%= f.text_area :outcome %>
<%= f.fields_for :purpose, #outcome.purpose.build do |p| %>
<%= p.text_area :desc, label: "Purpose" %>
<% end %>
<%= f.fields_for :action, #outcome.action.build do |p| %>
<%= p.text_area :desc, label: "Action" %>
<% end %>
<%= f.submit "submit" %>
<% end %>
Models:
# outcome.rb
has_many :purposes, :dependent => :destroy
has_many :actions, :dependent => :destroy
accepts_nested_attributes_of :purposes, :actions
-----------------------------------------
# purpose.rb
belongs_to :outcome
-----------------------------------------
# action.rb
belongs_to :outcome
Controller:
# outcomes_controller.rb
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:desc], action_attributes: [:desc])
end
SUGGESTION: You should rename your action model name to avoid unwanted conflicts with rails keyword action.
This may help you
Nestd Attributes
If the objects are associated (as below), you'll be best using the accepts_nested_attributes_for method:
#app/models/outcome.rb
Class Outcome < ActiveRecord::Base
has_many :purposes
has_many :actions
accepts_nested_attributes_for :purposes, :actions
end
#app/models/purpose.rb
Class Purpose < ActiveRecord::Base
belongs_to :outcome
end
#app/models/action.rb
Class Action < ActiveRecord::Base
belongs_to :outcome
end
accepts_nested_attributes_for means you'll be able to send the associated objects through the Outcome model - meaning you can send them all in a single HTTP request
You have to remember the way Rails is set up (MVC pattern), meaning if you send a single request; any further model objects you have will be able to be stored too.
Here's how you can set it up:
#app/controllers/outcomes_controller.rb
Class OutcomesController < ApplicationController
def new
#outcome = Outcome.new
#outcome.purposes.build
#outcoe.actions.build
end
def create
#outcome = Outcome.new(outcome_params)
#outcome.save
end
private
def outcome_params
params.require(:outcome).permit(:outcome, purpose_attributes:[:purpose], action_attributes: [:action])
end
end
Which will give you the ability to use this form:
#app/views/outcomes/new.html.erb
<%= form_for #outcome do |f| %>
<%= f.label :outcome %>
<%= f.text_area :outcome %>
<%= f.fields_for :purposes do |p| %>
<%= p.text_area :purpose %>
<% end %>
<%= f.fields_for :actions do |a| %>
<%= a.text_area :action %>
<% end %>
<%= f.submit %>
<% end %>
--
Recommendation
From the looks of it, I'd recommend you'll be able to keep all of these details in a single model - storing in multiple models seems overkill

Rails updating multiple models on a single form

Im writing a form which uses formtastic to manage the BusinessUnit model, however when creating a new BusinessUnit it also has to create a number of other record types. The associations between the models are as below:
class BusinessUnit < ActiveRecord::Base
has_many :business_unit_sites
has_many :locations
class BusinessUnitSite < ActiveRecord::Base
belongs_to :site
belongs_to :business_unit
class Site < ActiveRecord::Base
has_many :locations
has_many :business_unit_sites
class Location < ActiveRecord::Base
belongs_to :business_unit
belongs_to :site
When a BusinessUnit is created, a Site must also be created using BusinessUnitSite as a join table. In addition a Location record should be created which must hold a foreign key to the new Site record and this is where Im having problems.
I can create a new Location using a nested form (below) but the Site will have to be created manually.
<%= semantic_form_for #business_unit do |f| %>
<%= f.inputs do %>
<%= f.input :name %>
<%= f.input :business_unit_id %>
<%= f.input :business_unit_group, :include_blank => false %>
<%= f.input :business_unit_type %>
<%= f.input :tax_region, :include_blank => false %>
<%= f.semantic_fields_for :locations do |l| %>
<%= l.input :name, :label => "Location Name" %>
<% end %>
<% end %>
<%= f.buttons %>
<% end %>
What is the best way to create the Location, Site records and ensure that Location holds the foreign key of the newly created Site?
You probably want to do something like using the "fields_for" approach for the sub-objects in your form.
See this related answer:
Multiple objects in a Rails form
More info about fields_for:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for

Resources