nested attributes with polymorphic has_one model - ruby-on-rails

I am using accepts_nested_attributes_for with the has_one polymorphic model in rails 2.3.5
Following are the models and its associations:
class Address < ActiveRecord::Base
attr_accessible :city, :address1, :address2
belongs_to :addressable, :polymorphic => true
validates_presence_of :address1, :address2, :city
end
class Vendor < ActiveRecord::Base
attr_accessible :name, :address_attributes
has_one :address, :as => :addressable, :dependent => :destroy
accepts_nested_attributes_for :address
end
This is the view:
- form_for #vendor do |f|
= f.error_messages
%p
= f.label :name
%br
= f.text_field :name
- f.fields_for :address_attributes do |address|
= render "shared/address_fields", :f => address
%p
= f.submit "Create"
This is the partial shared/address_fields.html.haml
%p
= f.label :city
%br= f.text_field :city
%span City/Town name like Dharan, Butwal, Kathmandu, ..
%p
= f.label :address1
%br= f.text_field :address1
%span City Street name like Lazimpat, New Road, ..
%p
= f.label :address2
%br= f.text_field :address2
%span Tole, Marg, Chowk name like Pokhrel Tole, Shanti Marg, Pako, ..
And this is the controller:
class VendorsController < ApplicationController
def new
#vendor = Vendor.new
end
def create
#vendor = Vendor.new(params[:vendor])
if #vendor.save
flash[:notice] = "Vendor created successfully!"
redirect_to #vendor
else
render :action => 'new'
end
end
end
The problem is when I fill in all the fileds, the record gets save on both tables as expected.
But when I just the name and city or address1 filed, the validation works, error message shown, but the value I put in the city or address1, is not persisted or not displayed inside the address form fields?
This is the same case with edit action too.
Though the record is saved, the address doesn't show up on the edit form. Only the name of the Client model is shown.
Actually, when I look at the log, the address model SQL is not queried even at all.

Why f.fields_for :address_attributes?
Shouldn't it be:
- f.fields_for :address do |address_fields|
= render "shared/address_fields", :f => address_fields
It's not loading the values on edit and errors because you never load address_attributes with the values from #vendor.address.

Related

Rails - Set additional attribute on has_many through record

Using Rails 6.0.3.3 and ruby '2.6.0'.
I have these 3 models connected via a has_many through relation.
class Recipe < ApplicationRecord
has_many :layers
has_many :glazes, through: :layers
end
class Glaze < ApplicationRecord
has_many :layers
has_many :recipes, through: :layers
end
class Layer < ApplicationRecord
belongs_to :recipe
belongs_to :glaze
# there is a column named "coat_type" here that I would like to set
end
Everything is working great for creating a new Recipe and it automagically creating its related Layer record. But now I would like to also set a coat_type attribute on the Layer record when it's created, but I can't seem to figure out how I could do something like that.
The Form View Partial
= form_with model: recipe, local: true do |form|
%p
= form.label :name
%br
= form.text_field :name
%p
= form.label :description
%br
= form.text_area :description
.recipe-glaze
= form.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" }, { name: 'recipe[glaze_ids][]' })
.recipe-glaze
= form.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" }, { name: 'recipe[glaze_ids][]' })
%p
= form.submit
The Controller Create Action (and strong params acton)
def create
#recipe = Recipe.new(recipe_params)
if #recipe.save
redirect_to #recipe
else
render 'new'
end
end
private
def recipe_params
params.require(:recipe).permit(:name, :description, glaze_ids: [])
end
Ideally, I would be able to add another select box in the form and the user would use that to set the coat_type of the Layer record that will be created. But I can't figure out how I could pass that into the controller and have it know what to do with that value.
Is this something that is possible, or am I approaching this incorrectly?
So I actually ended up stumbling upon the "cocoon" gem thanks to this comment. By following the setup instructions for "cocoon", I was able to tweak my code to do what I needed.
My Recipe model changed to this ::
class Recipe < ApplicationRecord
has_many :layers, inverse_of: :recipe
has_many :glazes, through: :layers
accepts_nested_attributes_for :layers, reject_if: :all_blank, allow_destroy: true
end
My controller's strong params action changed to this ::
private
def recipe_params
params.require(:recipe).permit(:name, :description, layers_attributes: [:id, :glaze_id, :coat_type, :_destroy])
end
My form view partial changed to ::
- if recipe.errors.any?
= render partial: 'errors', locals: { recipe: recipe }
%p
= form.label :name
%br
= form.text_field :name
%p
= form.label :description
%br
= form.text_area :description
%h3 Layers
#layers
= form.fields_for :layers do |layer|
= render 'layer_fields', f: layer
.links
= link_to_add_association 'add layer', form, :layers
%p
= form.submit
and the "layer_fields" partial referenced in the form looks like this ::
.nested-fields
.field
= f.label :glaze_id
%br
= f.collection_select(:glaze_id, Glaze.all, :id, :name, { prompt: "Select Glaze" } )
.field
= f.label :coat_type
%br
= f.text_field :coat_type
= link_to_remove_association 'remove layer', f
Making those changes using the "Cocoon" gem, I was able to accomplish what I needed. Hopefully this helps someone else in the future.

Rails: Select multiple tag objects to associate

So I'm working on a project where I have a document object (basically an E-Library application) and I have a bunch of Tag objects that I want to be able to associate with it. Currently I have a has_and_belongs_to_many association between the two. My question is in the form for a tag, what is the best way to select from a list of available tags to associate with that document? And will I have to do any fancy work in the controller to make this happen?
I'm using rails 3.2
Here is some of the code:
# This is the text model
# It will not have an attachment but instead it's children will
class Text < ActiveRecord::Base
attr_accessible :name, :author, :date, :text_langs_attributes, :notes
has_many :text_langs, dependent: :destroy
belongs_to :item
validates :author, presence: true
has_and_belongs_to_many :tags
accepts_nested_attributes_for :text_langs
def get_translations
TextLang.where(:text_id => self.id)
end
def get_language(lang)
TextLang.where(:text_id => self.id, :lang => lang).first
end
end
This is the tag:
# This is the Tags class
# It has and belongs to all of the other file classes
# the tags will need to be translated into four langauges
# Tags will also own themselvea
class Tag < ActiveRecord::Base
attr_accessible :creole, :english, :french, :spanish, :cat,
:english_description, :french_description, :spanish_description,
:creole_description, :parent_id
has_and_belongs_to_many :texts
has_and_belongs_to_many :sounds
belongs_to :parent, :class_name => 'Tag'
has_many :children, :class_name => 'Tag', :foreign_key => 'parent_id'
validates :cat, presence: true, inclusion: { in: %w(main sub misc),
message: "%{value} is not a valid type of tag" }
validates :english, :spanish, :french, :creole, presence: true
TYPES = ["main", "sub", "misc"]
end
This is the form:
= form_for #text do |f|
- if #text.errors.any?
#error_explanation
%h2= "#{pluralize(#text.errors.count, "error")} prohibited this text from being saved:"
%ul
- #text.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :name
= f.text_field :name
.field
= f.label :date
= f.date_select :date
.field
= f.label :author
= f.text_field :author
= f.fields_for :text_langs do |pl|
.field
= pl.label :title
= pl.text_field :title
.field
= pl.label :lang
= pl.text_field :lang
.field
= pl.label :description
= pl.text_field :description, :size => 150
.field
= pl.label :plain_text
= pl.text_area :plain_text
.field
= pl.label :published
= pl.check_box :published
.field
= f.label :txt
= f.file_field :txt
.field
= f.label :notes
= f.text_area :notes, :rows => 10
.actions
= f.submit 'Save'
First of all I would suggest to try simple_form gem it would make your forms DRY and simple. They have very nice features for associations.
You would end doing something like this:
= simple_form_for #text do |f|
...
= f.association :tags, as: :check_boxes
Could be check boxes, radio buttons or maybe a select with multiple values if you need it.
Hope it helps

Accessing error messages for nested attribute field

I have a form created using the simple_form gem which populates 2 models using nested attributes. I want to check if there are any errors and display a new block. However, I'm not sure how to correctly access the error message for the location attribute of the Booking model.
class Booking < ActiveRecord::Base
belongs_to :customer
attr_accessible :date_wanted, :location
end
and
class Customer < ActiveRecord::Base
has_many :bookings
accepts_nested_attributes_for :bookings
attr_accessible :name, :phone, :bookings_attributes
validates_presence_of :name, :phone
end
Form view:
simple_form_for #customer, {:html => { :class => "form-horizontal" }} do |f|
= f.input :name
= f.input :phone
= f.simple_fields_for :bookings do |b|
= b.input :date
= b.input :location
- if #customer.errors[:appointments_attributes][:location]
# insert code if any validation errors for the date field were found
= f.button :submit
b is an instance of form builder, holding booking, so you can try:
# ...
if b.object.errors[:location]
# ...

Rails 3.1: Nested attributes won't save through the form

I suspect this might be a very simple mistake but I've spent 3 hours looking for it so I thought I might ask for some help from the community.
I'm running through Ryan Bates' excellent screencasts on Nested Models Forms and trying to apply them to my own project. The problem is the nested attribute doesn't seem to save using the form. I can get it to save through the console but it only shows up as empty brackets when going through the form.
Here's the relevant code:
The form view (using haml)
= form_for(#article) do |f|
- if #article.errors.any?
#error_explanation
%h2
= pluralize(#article.errors.count, "error")
prohibited this article from being saved:
%ul
- #article.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :title
%br/
= f.text_field :title
.field
= f.label :intro
%br/
= f.text_area :intro
= f.fields_for :subsections do |builder|
= render 'subsections_fields', :f => builder
.field
= f.label :published_at
%br/
= f.text_field :published_at
.actions
= submit_or_cancel(f)
subsection_fields form view
= f.label :header
%br/
= f.text_field :header
= f.label :order_id
= f.number_field :order_id
%br/
= f.label :body
%br/
= f.text_area :body
%br/
= f.check_box :_destroy
= f.label :_destroy, "Remove Subsection"
%br/
Controller
class ArticlesController < ApplicationController
def new
#article = Article.new
3.times { #article.subsections.build }
end
def create
#article = Article.new(params[:article])
if #article.save
flash[:notice] = "Successfully created article."
redirect_to #article
else
render :action => 'new'
end
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update_attributes(params[:article])
flash[:notice] = "Successfully updated article."
redirect_to #survey
else
render :action => 'edit'
end
end
def destroy
Article.find(params[:id]).destroy
flash[:notice] = "Succesfully destroy article."
redirect_to articles_url
end
def show
#article = Article.find(params[:id])
end
def index
#articles = Article.all
end
end
And the models
class Article < ActiveRecord::Base
attr_accessible :title, :intro
has_many :subsections, :dependent => :destroy
accepts_nested_attributes_for :subsections, :reject_if => lambda { |a| a[:body].blank? },
:allow_destroy => true
has_and_belongs_to_many :categories
validates :title, :presence => true
end
class Subsection < ActiveRecord::Base
attr_accessible :header, :body, :order_id
belongs_to :article
validates :header, :presence => true
validates :body, :presence => true
end
Any help figuring this out is much appreciated.
I'm not quite sure, but try it with attr_accessible :article_id as well in your Subsection model?
Adding "attr_accessible" to a model changes the way mass assignment works in rails.
If you remove the "attr_accessible" lines in your models then all your code will work perfectly as it is.
The class method "accepts_nested_attributes_for" adds a "subsections_attributes=(value)" method to your model.
The second you add "attr_accessible" to a model you now are forced into adding extra "attr_accessible" entries for each field that you want to assign via mass assignment. i.e. when you use Article.new(params[:article]).
I hope that was clear.
I figured out the answer to this one from another question.
The answer was to set my subsections_attributes as an attr_accessible, so the above Article model should look like this (I also added published_at as an attr_accessible):
class Article < ActiveRecord::Base
attr_accessible :title, :intro, :subsections_attributes, :published_at
has_many :subsections, :dependent => :destroy
accepts_nested_attributes_for :subsections, :reject_if => lambda { |a| a[:body].blank? },
:allow_destroy => true
has_and_belongs_to_many :categories
validates :title, :presence => true
end

Return Model and Associated Polymorphic Models

I am working with polymorphic associations and having some trouble. My models are setup like so:
class User < ActiveRecord::Base
has_one :phone, :as => :callable, :dependent => :destroy
end
class Client < ActiveRecord::Base
has_one :phone, :as => :callable, :dependent => :destroy
end
class Phone < ActiveRecord::Base
belongs_to :callable, :polymorphic => true
end
In my Users Controller
def create
#user = User.new(params[:user])
if #user.save
#user.phone.create(:area_code => params[:user][:area_code], :phone => params[:user][:phone])
redirect_to #user, :notice => "Account created successfully!"
else
render 'new'
end
end
In the development log I see where the phone and user are being inserted correctly, but when I go to edit the user, the fields for phone in the form are blank. Here is my edit method:
def edit_employee
#user = User.find(params[:id])
#title = "Edit #{#user.name}"
end
My edit user form looks like this.
- form_for #user do |f|
- if #user.errors.any?
.error_messages
%h2 Please correct the following errors
%ul
- for message in #user.errors.full_messages
%li= message
%p
= f.label :name, "Name"
= f.text_field :name
%p
= f.label :email, "Email Address"
= f.text_field :email
%p
= f.label :phone, "Phone"
= f.text_field :area_code, :style => "width: 50px;"
= f.text_field :phone, :style => "width: 100px;"
= f.label :ext, "Ext."
= f.text_field :extension, :style => "width: 60px;"
%p
= f.label :password, "Password"
= f.password_field :password
%p
= f.label :password_confirmation, "Confirm Password"
= f.password_field :password_confirmation
%p.button= f.submit
I know I should be adding something to this edit method, perhaps
#phone = #user.phone
But that didn't work either. This is the first go round with polymorphic associations so any help and and pointers are much appreciated. I watched the Railscasts on this topic but it didn't seem to follow my underlying functionality. Once again, thanks in advance for any help and let me know if any more information is needed!
you should look into using fields_for and nested_attributes. http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Do you have attr_accessible set in the user.rb model? If not I would add it because that is a security issue.
Ok I added the
accepts_nested_attributes_for :phone
in the user model. I also added the fields for phone in the new user form like so
%p
- fields_for #user.phone do |phone|
= phone.label :phone, "Phone"
= phone.text_field :area_code, :style => "width: 50px;"
= phone.text_field :phone, :style => "width: 100px;"
= phone.label :ext, "Ext."
= phone.text_field :extension, :style => "width: 60px;"
but now I am getting the ActionView::Template::Error (undefined method `model_name' for NilClass:Class) exception.
And yes I have attr_accessible in my model. I just placed a very watered down version in here.

Resources