I can't seem to get a has_one relationship with accepts_nested_attributes_for to work. This is my first foray into nested attributes, so it might be incorrect. Help!
Models:
class Case < ActiveRecord::Base
has_one :logged_event, class_name: 'Event', dependent: :destroy
accepts_nested_attributes_for :logged_event
end
class Event < ActiveRecord::Base
belongs_to :case
belongs_to :user
validates :case, presence: true
validates :user, presence: true
end
Controller:
class CasesController < ApplicationController
load_and_authorize_resource
def new
#case.build_logged_event(user: current_user)
end
def create
if #case.save
flash[:notice] = 'Case was successfully logged.'
redirect_to cases_path
else
render 'cases/new'
end
end
end
Form:
<h1>New Case</h1>
<%= error_messages_for :case %>
<%= form_for(#case) do |form| %>
<p>
<%= form.label(:study) %>:
<%= form.select(:study, Case.options_for(:study), include_blank: true) %>
</p>
<%= form.fields_for(:logged_event) do |logged_event_form| %>
<%= logged_event_form.label(:created_at, 'Date Case was opened:') %>
<%= logged_event_form.select(:created_at, [['Today', Date.today], ['Yesterday', Date.yesterday]]) %>
<% end %>
<%= form.submit('Log Case') %> or <%= link_to('cancel', cases_path) %>
<% end %>
It's saying that the logged event is not getting a user id or a case id, so both validations on the event class are failing. It looks like:
There were problems with the following fields:
Logged event case can't be blank
Logged event user can't be blank
I don't get that, because in the new action I have already built the object. I also tried form.fields_for(:logged_event, #case.logged_event) but that didn't work either.
It's because new is not being called when you submit the form. Only the create action is being ran. So #case is populated only with the for submission data; no user or case is assigned to it since it's not in the form.
You'd need to assign the user and case properties of #case before trying to save (and running validations)
Turns out you shouldn't validate on both sides. I was validating that a case had an event and an event had a case. By removing the validates :case, presence: true like from event, everything worked as it should have.
You don't need to remove your validation -- you just need to add inverse_of to the association declaration to tell Rails to manage the back reference for you.
Your models should look like this:
class Case < ActiveRecord::Base
has_one :logged_event, class_name: 'Event', dependent: :destroy, inverse_of: :case
accepts_nested_attributes_for :logged_event
end
class Event < ActiveRecord::Base
belongs_to :case, inverse_of: :logged_event
belongs_to :user, inverse_of: :logged_event # or inverse_of: :event -- you didn't provide your User model
validates :case, presence: true
validates :user, presence: true
end
Related
I was wondering if someone could help me out with an application that has some ecommerce characteristics.
Context: Via the application a bike shop chain ('chains') can rent out
bikes ('bikes'),
by picking out a bike type such as mountainbike, city bike etc. ('bike_types) and
bike options, such as helmets etc. ('bike_options')
which are dependent on the individual bike store ('bike_stores')
this rental of the bikes & options will all be captured in an order ('orders')
the relationship between orders and bikes is many-to-many, therefore I created a table to bridge this ('order_bikes')
Final notes:
Before the rental process, the chain owner first created his/her (i) bike_stores, (ii) bike_types, (iii) bikes and (iv) bike_options, this part of the application is working. Therefore, he/she only needs to select bike_types/bikes/options out of the existing inventory previously created.
I limit the scope of the question by leaving out the bike_options, this was mainly to provide some context in order to understand the db schema build up.
Error message: Unpermitted parameter: :bike_id
Code:
models
class Order < ApplicationRecord
belongs_to :bike_store
has_many :bike_types, through: :bike_store
has_many :order_bikes, inverse_of: :order, dependent: :destroy
accepts_nested_attributes_for :order_bikes, allow_destroy: true
end
class OrderBike < ApplicationRecord
belongs_to :bike
belongs_to :order
accepts_nested_attributes_for :bike
end
class Bike < ApplicationRecord
belongs_to :bike_type
validates :name, presence: true
has_many :order_bikes
has_many :orders, through: :order_bikes
end
class BikeType < ApplicationRecord
belongs_to :bike_store
has_many :bikes, dependent: :destroy
accepts_nested_attributes_for :bikes, allow_destroy: true
has_many :bike_options, dependent: :destroy
accepts_nested_attributes_for :bike_options, allow_destroy: true
validates :name, :bike_count, presence: true
end
class BikeStore < ApplicationRecord
has_many :bike_types, dependent: :destroy
has_many :orders, dependent: :destroy
end
Order controller
class OrdersController < ApplicationController
def new
#bike_store = BikeStore.find(params[:bike_store_id])
#order = Order.new
#order.order_bikes.build
#bike_type_list = #bike_store.bike_types
end
def create
#order = Order.new(order_params)
#bike_store = BikeStore.find(params[:bike_store_id])
#order.bike_store = #bike_store
#order.save
redirect_to root_path
end
private
def order_params
params.require(:order).permit(:arrival, :departure,
order_bikes_attributes: [:id, :bike_quantity, :_destroy,
bikes_attributes: [:id, :name,
bike_types_attributes: [:id, :name]]])
end
end
view
<%= simple_form_for [#bike_store, #order] do |f|%>
<%= f.simple_fields_for :order_bikes do |order_bike| %>
<%= order_bike.input :bike_quantity %>
<%= order_bike.association :bike %>
<% end %>
<%= f.input :arrival %>
<%= f.input :departure %>
<%= f.submit %>
<% end %>
If you check coed from simple form here, you will see what actually method association does.
def association(association, options = {}, &block)
# ... simple form code here ...
attribute = build_association_attribute(reflection, association, options)
input(attribute, options.merge(reflection: reflection))
end
We are interested in build_association_attribute method call. here
def build_association_attribute(reflection, association, options)
case reflection.macro
when :belongs_to
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
# ... the rest of code ...
end
end
Your order bike model has belongs_to :bike association. So when you call order_bike.association :bike it builds :bike_id attribute in your form. If you check params hash that comes to your controller, I believe you'll see that attribute coming from your view.
I added bike_id to permitted parameters. I hope it will fix your problem..
def order_params
params.require(:order).permit(:arrival, :departure,
order_bikes_attributes: [:id, :bike_id, :bike_quantity, :_destroy,
bikes_attributes: [:id, :name,
bike_types_attributes: [:id, :name]]])
end
My models look like this:
class Project < ApplicationRecord
has_many :comments
has_many :contractor_projects
has_many :contractors, through: :contractor_projects
validates_presence_of :title, :contract_number, :category, :project_start_date, :project_end_date, :substantial_completion_date, :category, :solicitation_number, :project_officer, :location
accepts_nested_attributes_for :contractor_projects
end
class Contractor < ApplicationRecord
has_many :contractor_projects
has_many :projects, through: :contractor_projects
validates :name, presence: true
validates :email, presence: true, uniqueness: true
end
class ContractorProject < ApplicationRecord
belongs_to :contractor
belongs_to :project
end
The ContractorProject model has an extra attribute #bid_status that I want to reflect on project's show page but it does not appear even though it's in the params when i raised it.
below is sample method for your case
def show
#project = Project.find(params[:id]
#contractors = #project.contractors
end
inside show.html.erb, you have to loop it, since it may get more than one records
<% #contractors.each do |contractor| %>
<%= contractor.bid_status %>
<% end %>
====== CODE =====
Model father.rb & son.rb
class Father < ApplicationRecord
has_many :sons
validates :f_name, presence: true
accepts_nested_attributes_for :sons
end
class Son < ApplicationRecord
belongs_to :father
validates :s_name, presence: true
end
_form.html.erb--fathers
<%= form_for #order do |f| %>
<%= f.text_field :f_name %>
<%= f.fieids_for :sons do |ff| %>
<%= ff.s_name %>
<% end %>
<% end %>
fathers_controller.rb
def create
#father = Father.new father_params
if #father.save
do_something
end
end
======= QUESTION ======
If I save father object like below, it will do validations for both father and son.
Instead, if I change it to #father.save(validate: false), I think this will jump both validations.
What I want is only to do validation for father's attribute.
Is there a way to achieve this?
I think you should add the code below to your model father.rb so that basicly you are rejecting son's attribute from validation and only the father's attribute will be validated:
reject_if: proc { |attributes| attributes['s_name'].blank? }
The final model father.rb will be:
class Father < ApplicationRecord
has_many :sons
validates :f_name, presence: true
accepts_nested_attributes_for :sons,
reject_if: proc { |attributes| attributes['s_name'].blank? }
end
UPDATE
( NOTE: The code above won't save :s_name attribute into DB, as #Marco Song didn't mention in his question)
The solution which works for me, is to add allow_blank: true to Son model. allow_nil: true didn't work for me in Rails 5.0.0.1, Ruby 2.3.1
Also I added inverse_of: to both models to avoid SQL queries, not generating them.
Father model:
app/models/father.rb
class Father < ApplicationRecord
has_many :sons, inverse_of: :father
validates :f_name, presence: true
accepts_nested_attributes_for :sons
end
Son model:
app/models/son.rb
class Son < ApplicationRecord
belongs_to :father, inverse_of: :sons
validates :s_name, presence: true, allow_blank: true
end
I whitelisted son's attributes in Father's controller:
def father_params
params.require(:father).permit(:f_name, sons_attributes: [:id, :s_name])
end
At the end after this setup above, I was able to save :s_name attribute in the DB.
I am a Rails newbie trying to accept nested attributes for model Address through model Vault 'new' form, but I am getting an undefined method `build' for nil:NilClass ERROR
I have two Models, a Vault Model here:
class Vault < ActiveRecord::Base
has_one :address, dependent: :destroy
accepts_nested_attributes_for :address, allow_destroy: true
end
and I also have an Address Model here:
class Address < ActiveRecord::Base
belongs_to :vault
end
This is my Vault_controller 'new' methodd:
def new
#vault = Vault.new
#vault.address.build
end
This is part of my _form.html.erb Vault partial, where I am trying to capture the info for the Address model:
<%= f.fields_for :address do |builder| %>
<%= builder.label :stnumber, "St. Number" %></br>
<%= builder.text_field :stnumber %>
<% end %>
#vault.rb
class Vault < ActiveRecord::Base
has_one :address, dependent: :destroy
#Other codes goes here
end
#address.rb
class Address < ActiveRecord::Base
belongs_to :vault
#other code goes here.
end
Build Address(depending on association)
v = Vault.new
address = v.address.build
# this one will work only for has_many association.
address = v.build_address
#this one will work for your has_one association
Please check this link.
I have a library-like booking system. I want to make a form for adding books in stock, allowing the user to choose a book and choose a library (both are collection_select). Book has a many-to-many relationship with Library, through the stock_items table.
What I can't figure out is how can bring in the quantity, so that the user can add a number of instances of the same book to a chosen university. How should I approach implementing this quantity-type feature. It should create a chosen amount of records in the join table.
Here's my form (currently creates only 1 instance at a time):
<%= form_for(#item) do |f| %>
<%= f.label :choose_book %>
<%= f.collection_select(:book_id, Book.all, :id, :name, prompt: true) %>
<%= f.label :choose_library %>
<%= f.collection_select(:library_id, Library.all, :id, :name, prompt: true) %>
<%= f.submit "Add item in stock", class: "btn btn-info" %>
<% end %>
StockItem model
class StockItem < ActiveRecord::Base
belongs_to :library
belongs_to :book
has_many :bookings, foreign_key: :stock_id, dependent: :destroy
validates :availability, presence: true
validates :library_id, presence: true
end
Library model
class Library < ActiveRecord::Base
has_many :stock_items
has_many :books, through: :stock_items
end
Book model
class Book < ActiveRecord::Base
validates :year_of_publication, presence: true, length: { maximum: 4 }
validates :description, presence: true, length: { minimum: 10 }
validates :name, presence: true
has_many :stock_items, dependent: :destroy
has_many :libraries, through: :stock_items
has_many :contributions, dependent: :destroy
has_many :authors, through: :contributions
has_many :bookings, through: :stock_items
has_many :book_images, dependent: :destroy
accepts_nested_attributes_for :book_images
accepts_nested_attributes_for :authors
accepts_nested_attributes_for :libraries
accepts_nested_attributes_for :stock_items
accepts_nested_attributes_for :contributions
validates :name, presence: true
end
A bit of the StockItemsController
def create
#item = StockItem.new(item_params)
if #item.save
flash[:success] = "Item added to stock"
redirect_to stock_items_path
else
flash[:danger] = "Item has not been added to stock!"
render 'new'
end
end
def new
#item = StockItem.new
end
private
def item_params
params.require(:stock_item).permit(:library_id, :book_id, :availability)
end
I think your answer is systemic, rather than syntaxic, meaning you have to consider your system rather than the specific action syntax
--
M-to-M
You specifically need to look at the many-to-many association you've deployed here
Your population of the StockItem table is basically a way to create a collection for each library, and so you should look at the many collection based methods which ActiveRecord provides (specifically << and .delete)
Bottom line is that instead of creating new records for your StockItem model, I would just add to the collection of both library - giving you the files you need to make it work
--
Collection
As you're using has_many :through, you can add multiple instances of the same record to your collection (as opposed to has_and_belongs_to_many, which only permits single instances of records, as there is no primary_key)
This means you'll be able to just add books individually to the collection, and then use the sum method of SQL to calculate the quantity:
#app/controllers/stock_items_controller.rb
class StockItemsController < ApplicationController
def create
#library = Library.find params[:stock_item][:library_id]
#book = Book.find params[:stock_item][:book_id]
#library << #book
end
end
--
Quantity
This is opposed to the idea that you can add a qty attribute to your StockItem model, and then use the increment! method:
#app/controllers/stock_items_controller.rb
Class StockItemsController < ApplicationController
def create
#item = StockItem.find_by(library_id: params[:stock_item][:library_id],book_id: params[:stock_item][:book_id])
#item ? #item.increment!(:qty) : StockItem.create(stockitem_params)
end
end