Form for has_many :trough association with creating object - ruby-on-rails

Problem
With my rails app I'm trying to accomplish something like image below.
Invoice should has many Items. For example Invoice_1 would have in invoice_items container:
[{item: Egg, quantity: 3, unit_price: 4}, {item: Stick, quantity: 4, unit_price: 2}].
How to made a form to add n Items to Invoice?
Or how to edit this form/controller to create fixed number of InvoiceItems for a start. Later I'll figure how to create Items and create associations dynamically in html.
Source
Models
class Item < ActiveRecord::Base
has_many :invoice_items
has_many :invoices, through: :invoice_items
end
class Invoice < ActiveRecord::Base
has_many :invoice_items, inverse_of: :invoice
has_many :items, through: :invoice_items
accepts_nested_attributes_for :invoice_items
validates :items, :length => { :minimum => 1 }
end
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :item
validates_presence_of :invoice
validates_presence_of :item
accepts_nested_attributes_for :item
end
Invoice controller
# GET /invoices/new
def new
#invoice = Invoice.new
#invoice.invoice_items.build
end
private:
def invoice_params
#params.fetch(:invoice, {})
params.require(:invoice).permit(
:date,
:seller_id,
:client_id,
invoice_items_attributes: [ item_attributes:
[:name, :quantity, :unit, :unit_price_cents, :unit_price_currency, :price_cents, :price_currency ]
],
invoice_name_attributes: [:number, :month, :year],
)
end
Invoice form
<%= form_for(#invoice) do |invoice_form| %>
<%= invoice_form.fields_for :invoice_items do |invoice_item_form| %>
<%= invoice_item_form.fields_for :item do |item_form| %>
<%= item_form.text_field :name %>
<% end %>
<% end %>
<div class="actions">
<%= invoice_form.submit %>
</div>
<% end %>

Based on what I'm reading, you probably just want to focus on making InvoiceItem records built from an invoice. You can build a fixed number (n) of invoice items by using code such as n.times { invoice.invoice_items.build } in the new method in your controller.
When you want to create dynamic fields there is a gem called Cocoon which allows you to dynamically add associated records to a form.
My advice is just to use fields_for :invoice_items as you have done already in your form and omit the fields_for :items. Instead in the invoice_items form add a select_tag that includes all of the Items you want to add to an invoice.

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

Nested checkboxes in Rails

I'm trying to create an event app where each event has multiple tables and each table has multiple people sitting at a table the event has multiple tickets which map the people to the tables that they are sitting at -> in order to achieve this I have created a checkbox nested in the fields_for :tables (which is in turn in the event form) I presume something is wrong with either the strong parameters or the form itself but I have not been able to find any information that provides a solution to the problem.After checking the checkboxes in the form indicating which people are going to be sitting at this table and submitting the form and returning to the form I find that the checkboxes are no longer checked???
here are the contents of my model files
# models
class Event < ActiveRecord::Base
has_many :tables, dependent: :destroy
has_many :people , through: :tickets
has_many :tickets
accepts_nested_attributes_for :tickets, allow_destroy: true
accepts_nested_attributes_for :tables, allow_destroy: true
end
class Table < ActiveRecord::Base
belongs_to :event
has_many :tickets
has_many :people, through: :tickets
end
class Ticket < ActiveRecord::Base
belongs_to :table
belongs_to :person
end
class Person < ActiveRecord::Base
has_many :tickets
has_many :tables, through: :tickets
end
Here is the form with parts omitted for brevity.
<%= form_for(#event) do |f| %>
...
<%= f.fields_for :tables do |builder| %>
<%= render 'table_field', f: builder %>
<% end %>
<%= link_to_add_fields "Add Table", f, :tables %>
...
<% end %>
And here is the checkbox list I have implemented within the table_field.
<% Person.all.each do |person| %>
<div class="field">
<%= check_box_tag "table[people_ids][]", person.id, f.object.people.include?(person) %> <%= f.label [person.first_name, person.last_name].join(" ") %>
</div>
<% end %>
this is the event_params
def event_params
params.require(:event).permit(:name, :description, :start, :end, :latitude, :longitude, :address, :data, :people_ids => [], tables_attributes: [:id, :number, :size, :people_ids => []]).tap do |whitelisted|
whitelisted[:data] = params[:event][:data]
end
How do I get the checkboxes to be persistently checked in this form?
You can use http://apidock.com/rails/v4.0.2/ActionView/Helpers/FormOptionsHelper/collection_check_boxes
<%= f.collection_check_boxes(:people_ids, Person.all, :id, :name) do |person| %>
<%= person.label { person.check_box } %>
<% end %>
It will persist data as well.

Rails form multiple checkboxes for associated model

I'm creating an application where a "submission" can be made using a form which creates client details and allows "referrals" to be created depending on the branch(es) that can provide the required service
class Submission < ActiveRecord::Base
has_many :referrals, :inverse_of => :submission, dependent: :delete_all
accepts_nested_attributes_for :referrals, :allow_destroy => true
end
class Referral < ActiveRecord::Base
belongs_to :submission
end
class Branch < ActiveRecord::Base
has_many :referrals
end
Submissions controller:
def new
#submission = Submission.new
#submission.build_client
#submission.client.build_address
#submission.referrals.build
end
def submission_params
params.require(:submission).permit(:consent, :user_id, client_attributes:
[:client_id, :first_name,
address_attributes:
[:first_line, :second_line,]
],
referrals_attributes:
[:branch_id]
)
end
The Submission form:
<%= form_for(#submission) do |f| %>
<%= f.fields_for :referrals do |referral| %>
<%= render 'referral_fields', f: referral %>
<% end %>
<% end %>
_referral_fields.html.erb:
<% Branch.all.where(referrable: true).each do |branch| %>
<label>
<%= check_box_tag 'branch_ids[]', branch.id %>
<%= branch.name %>
</label>
<% end %>
What I want is to have checkboxes for each referrable branch. When a branch is ticked and the submission is created, a referral will be created for that branch. However, when I submit the form, I get a validation error of "Referrals can't be blank". Any idea why this is not working?
Any help is most appreciated
Use collection_check_boxes.
<% # _referral_fields.html.erb %>
<%= f.collection_check_boxes(:branch_ids, Branch.where(referrable: true), :id, :name) do |b|
b.label { b.check_box } # wraps check box in label
end %>
You would need to whitelist submission[referrals_attributes][branch_ids] - not branch_id.
def submission_params
params.require(:submission)
.permit(
:consent,
:user_id,
client_attributes: [
:client_id,
:first_name,
address_attributes: [
:first_line, :second_line,
]
],
referrals_attributes: [:branch_ids]
)
end
Edited.
However for this to work you need to setup a relation between Referral and Branch. In this case you could use either a has_and_belongs_to_many (HABTM) or has_many though: (HMT) relationship.
See Choosing Between has_many :through and has_and_belongs_to_many.
class Referral < ActiveRecord::Base
belongs_to :submission
has_and_belongs_to_many :branches
end
class Branch < ActiveRecord::Base
has_and_belongs_to_many :referrals
end
You need to create a join table as well:
rails g migration CreateBranchReferralJoinTable branch referral

has_many through form listing each record of the other parent

I'm trying to create an order form that also serves as a list for all the available products. I have the models with the has_many through associations, and I managed to create collection_select fields to create the records on the join model which works fine, but I cannot figure out how to build a list showing each product and accepting additional attributes for the join records.
My models (somewhat simplified, I also have images and such):
class Supply < ActiveRecord::Base
attr_accessible :available, :name
has_many :order_lines
has_many :orders, through: :order_lines
accepts_nested_attributes_for :order_lines
end
class Order < ActiveRecord::Base
attr_accessible :month, :user_id, :order_lines_attributes
belongs_to :user
has_many :order_lines
has_many :supplies, through: :order_lines
accepts_nested_attributes_for :order_lines
end
class OrderLine < ActiveRecord::Base
attr_accessible :amount, :supply_id, :order_id
belongs_to :order
belongs_to :supply
validates_presence_of :amount
end
Order controller:
def new
#order = Order.new
#supplies = Supply.available
#supplies.count.times { #order.order_lines.build }
respond_to do |format|
format.html # new.html.erb
format.json { render json: #supply_order }
end
end
Order_line field from Order form:
<%= f.fields_for :order_lines do |order_line| %>
<%= order_line.label :amount %>
<%= order_line.number_field :amount %>
<%= order_line.label :supply_id %>
<%= order_line.collection_select(:supply_id, #supplies, :id, :name) %>
<% end %>
In place of this I would like to have a list with one order_line per supply (with name and other attributes) which gets saved if there's an amount defined. Any help appreciated!
Just add an order_item for each supply. Instead of this:
#supplies.count.times { #order.order_lines.build }
Do this:
#supplies.each { |supply| #order.order_lines.build(supply: supply) }
Then in your order form you don't need the collection_select for supply_id, just show the name:
<%= order_line.object.supply.name %>
Make sure you ignore nested attributes with an empty amount:
accepts_nested_attributes_for :order_lines, reject_if: proc { |attr| attr.amount.nil? || attr.amount.to_i == 0 }
You don't need the accepts_nested_attributes_for in the Supply model.

Nested forms with multiple table inheritance

How do I build a nested form for objects using multiple table inheritance in rails? I am trying to make a nested form to create an object using a model with a has_many relationship to another set of models that feature multi-table inheritance. I am using formtastic and cocoon for the nested form and the act_as_relation gem to implement the multiple table inheritance.
I have the following models:
class Product < ActiveRecord::Base
acts_as_superclass
belongs_to :store
end
class Book < ActiveRecord::Base
acts_as :product, :as => :producible
end
class Pen < ActiveRecord::Base
acts_as :product, :as => :producible acts_as :product, :as => :producible
end
class Store < ActiveRecord::Base
has_many :products
accepts_nested_attributes_for :products, :allow_destroy => true, :reject_if => :all_blank
end'
For this example, the only unique attribute that book has compared to other products is an author field. In reality, I have a number of unique attributes for book which is why I chose multi-table inheritance over the more commonplace single table inheritance.
I am trying to create a nested form that allows you to create a new store with products. Here's my form:
<%= semantic_form_for #store do |f| %>
<%= f.inputs do %>
<%= f.input :name %>
<h3>Books/h3>
<div id='books'>
<%= f.semantic_fields_for :books do |book| %>
<%= render 'book_fields', :f => book %>
<% end %>
<div class='links'>
<%= link_to_add_association 'add book', f, :books %>
</div>
<% end %>
<%= f.actions :submit %>
<% end %>
And the book_fields partial:
<div class='nested-fields'>
<%= f.inputs do %>
<%= f.input :author %>
<%= link_to_remove_association "remove book", f %>
<% end %>
</div>
I get this error:
undefined method `new_record?' for nil:NilClass
Based on reading the issues on the github page for act_as_relation, I thought about making the relationship between store and books more explicit:
class Product < ActiveRecord::Base
acts_as_superclass
belongs_to :store
has_one :book
accepts_nested_attributes_for :book, :allow_destroy => true, :reject_if => :all_blank
end
class Book < ActiveRecord::Base
belongs_to :store
acts_as :product, :as => :producible
end
class Store < ActiveRecord::Base
has_many :products
has_many :books, :through => :products
accepts_nested_attributes_for :products, :allow_destroy => true, :reject_if => :all_blank
accepts_nested_attributes_for :books, :allow_destroy => true, :reject_if => :all_blank
end
Now, I get a silent error. I can create new stores using the form, and cocoon allows me to add new book fields, but when I submit the store gets created but not the child book. When, I go through the `/books/new' route, I can create a new book record that spans (the products and books table) with no problem.
Is there a workaround to this problem? The rest of the code can be found here.
Maybe you could:
Build the books relation manually on your stores_controller#new action
#store.books.build
Store manually the relation on you stores_controller#create action
#store.books ... (not really confident on how to achieve it)
Keep us posted.
You might want to consider creating your own form object. This is a RailsCast pro video, but here are some of the examples in the ASCIIcast:
def new
#signup_form = SignupForm.new(current_user)
end
This signup form can include relations to your other objects, just as you would in your original controller code:
class SignupForm
# Rails 4: include ActiveModel::Model
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
validates_presence_of :username
validates_uniqueness_of :username
validates_format_of :email, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/
validates_length_of :password, minimum: 6
def persisted?
false
end
def subscribed
subscribed_at
end
def subscribed=(checkbox)
subscribed_at = Time.zone.now if checkbox == "1"
end
def generate_token
begin
self.token = SecureRandom.hex
end while User.exists?(token: token)
end
end
Here is the link to the RailsCast. Getting a pro membership might be worth your time. I have been getting lucky with a membership through www.codeschool.com where you can get 'prizes' when you finish courses:
RailsCast:
http://railscasts.com/episodes/416-form-objects

Resources