I do not really understand how nested attributes work in Rails.
I have 2 models, Accounts and Users. Account has_many Users. When a new user fills in the form, Rails reports
User(#2164802740) expected, got Array(#2148376200)
Can Rails not read the nested attributes from the form? How can I fix it? How can I save the data from nested attributes to the database?
Thanks all~
Here are the MVCs:
Account Model
class Account < ActiveRecord::Base
has_many :users
accepts_nested_attributes_for :users
validates_presence_of :company_name, :message => "companyname is required."
validates_presence_of :company_website, :message => "website is required."
end
User Model
class User < ActiveRecord::Base
belongs_to :account
validates_presence_of :user_name, :message => "username too short."
validates_presence_of :password, :message => "password too short."
end
Account Controller
class AccountController < ApplicationController
def new
end
def created
end
def create
#account = Account.new(params[:account])
if #account.save
redirect_to :action => "created"
else
flash[:notice] = "error!!!"
render :action => "new"
end
end
end
Account/new View
<h1>Account#new</h1>
<% form_for :account, :url => { :action => "create" } do |f| %>
<% f.fields_for :users do |ff| %>
<p>
<%= ff.label :user_name %><br />
<%= ff.text_field :user_name %>
</p>
<p>
<%= ff.label :password %><br />
<%= ff.password_field :password %>
</p>
<% end %>
<p>
<%= f.label :company_name %><br />
<%= f.text_field :company_name %>
</p>
<p>
<%= f.label :company_website %><br />
<%= f.text_field :company_website %>
</p>
<% end %>
Account Migration
class CreateAccounts < ActiveRecord::Migration
def self.up
create_table :accounts do |t|
t.string :company_name
t.string :company_website
t.timestamps
end
end
def self.down
drop_table :accounts
end
end
User Migration
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :user_name
t.string :password
t.integer :account_id
t.timestamps
end
end
def self.down
drop_table :users
end
end
Thanks everyone. :)
Change the following view region:
<% form_for :account, :url => { :action => "create" } do |f| %>
into:
<% form_for #account do |f| %>
Inside your controller you should have something like this:
def new
#account = Account.new
# the new empty account doesn't have any users
# so the user fields inside your view won't appear unless you specify otherwise:
#account.users.build
#account.users.build
#account.users.build
end
Related
I am new to ruby on rails and I am trying to store nested json in the table.
json:
articles: {
title: "abc",
text: "a",
address: {
flat: "abc",
city: "bang"
}
}
Migrations:
class CreateArticles < ActiveRecord::Migration[5.2]
def change
create_table :articles do |t|
t.string :title
t.text :text
t.string :address
t.timestamps
end
end
end
class CreateAddresses < ActiveRecord::Migration[5.2]
def change
create_table :addresses do |t|
t.string :flat
t.string :city
t.timestamps
end
end
end
models:
class Article < ApplicationRecord
has_one :address
accepts_nested_attributes_for :address
end
class Address < ApplicationRecord
end
controller:
class ArticlesController < ApplicationController
def create
#article = Article.new(params.require(:article).permit(:title, :text, :address))
#article.save
redirect_to #article
end
def show
#article = Article.find(params[:id])
end
end
form(new.html.erb):
<%= form_with scope: :article, url: articles_path, local: true do |form| %>
<p>
<%= form.label :title %><br>
<%= form.text_field :title %>
</p>
<p>
<%= form.label :text %><br>
<%= form.text_area :text %>
</p>
<%=form.fields_for :address do |a| %>
<div>
<%=a.label :flat%><br>
<%= a.text_field :flat%><br>
<%=a.label :city%><br>
<%= a.text_field :city%>
</div>
<%end%>
<p>
<%= form.submit %>
</p>
I am not able to store the adrress to the table. address is always saved as nil. Can anyone guide me if what i am doing wrong. I want to parse the json to the table and store the json as string. Updated the question with controller and form that i am using.
When you want to permit nested attributes you do specify the attributes of nested object within an array. Pls, try this one #article = params.require(:articles).permit(:text, :title, :address =>[:flat, :city])
Rails has a very good documentation pls take a look https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit
I saw a similar answered question here which got me this far. But now I am facing an error in Form. The solution I am looking for is basically saving to two tables in Ruby Rails where saving the Property with address in first table also saves 2 images in Pictures' second table.
Migration1:
class CreateProperties < ActiveRecord::Migration[5.0]
def change
create_table :properties do |t|
t.string :address
t.timestamps
end
end
end
Migration2:
class CreatePictures < ActiveRecord::Migration[5.0]
def change
create_table :pictures do |t|
t.string :image1
t.string :image2
t.timestamps
end
end
end
Property Model:
class Property < ApplicationRecord
has_many :pictures
accepts_nested_attributes_for :pictures
end
Picture Model:
class Picture < ApplicationRecord
belongs_to :property
end
PropertiesController:
class PropertiesController < ApplicationController
before_action :set_property
def new
#property = Property.new
end
def create
#property = properties.build(property_params)
if #property.save
flash[:success] = "Property was successfully created"
redirect_to property_path(#property)
else
render 'new'
end
end
private
def property_params
params.require(:property).permit(:address, picture_attributes: [:image1, :image2])
end
end
The FORM which I don't know is done as below:
<%= form_for(#property) do |f| %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.label :image1 %>
<%= f.text_field :image1 %>
<%= f.label :image2 %>
<%= f.text_field :image2 %>
<%= f.submit %>
<% end %>
Error Picture:
Error on new.html.erb
You should use the fields_for method to have a form for pictures inside the property form:
# inside the property form_for
<%= f.fields_for #property.pictures.build do |p| %>
<%= p.file_field :image1 %>
<%= p.file_field :image2 %>
<% end %>
I'm trying to add a user profile sub module to a user module but having some problems.
Routes:
resources :users do
resources :userprofiles
end
userprofiles_controller.rb:
class UserprofilesController < ApplicationController
def edit
#user = current_user
#user.UserProfile ||= UserProfile.new
#userprofile = #user.UserProfile
end
def update
#user = current_user
#user.UserProfile ||= UserProfile.new
#userprofile = #user.UserProfile
if #userprofile.update_attributes(:userprofile => params[:userprofile])
redirect_to #user
flash[:notice] = "Changes saved."
else
render 'edit'
flash[:notice] = "Error."
end
end
end
user_profile.rb:
class UserProfile < ActiveRecord::Base
attr_accessible :first_name, :last_name, :summary
belongs_to :user
end
Error:
Can't mass-assign protected attributes for UserProfile: userprofile
Line:
if #userprofile.update_attributes(:userprofile => params[:userprofile])
EDIT
Form:
<%= form_for([#user, #userprofile], url: user_userprofile_path(#user, #userprofile)) do |form| %>
<%= form.label :first_name %>
<%= form.text_field :first_name %>
<%= form.label :last_name %>
<%= form.text_field :last_name %>
<%= form.label :summary %>
<%= form.text_area :summary %>
<%= form.submit "Update", class: "btn btn-block btn-primary" %>
<% end %>
Table:
create_table "user_profiles", force: true do |t|
t.string "last_name"
t.string "first_name"
t.text "summary"
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
You just want
#userprofile.update_attributes(params[:userprofile])
That's a hash with keys :first_name, :last_name, and :summary, which are allowed attributes. When you try to update :userprofile => params[:userprofile], the model checks to see if the key :userprofile is allowed - and it isn't.
I also had this problem. The issue is that you still have attr_accessible in your model controller. Since you don't need them anymore with Rails 4 remove them, add your strong parameters to the controller, and you'll be able to mass-assign without issue.
I have set up a HABTM relationship between two table creating a many to many relationship between items and categories. I want to add an item connected with one or more categories via the add item form. when I submit the form I am getting the error "Can't mass-assign protected attributes: categories".
Here are my models:
class Item < ActiveRecord::Base
attr_accessible :description, :image, :name
has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
belongs_to :user
has_and_belongs_to_many :categories
validates :name, presence: true, length: {maximum: 50}
accepts_nested_attributes_for :categories
end
class Category < ActiveRecord::Base
attr_accessible :description, :name
has_and_belongs_to_many :items
validates :name, presence: true, length: {maximum: 50}
end
And my migrations:
class CreateItems < ActiveRecord::Migration
def change
create_table :items do |t|
t.string :name
t.text :description
t.has_attached_file :image
t.timestamps
end
end
end
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.string :name
t.string :description
t.timestamps
end
end
end
class CreateCategoriesItems < ActiveRecord::Migration
def up
create_table :categories_items, :id => false do |t|
t.integer :category_id
t.integer :item_id
end
end
def down
drop_table :categories_items
end
end
And my form looks like this:
<%= form_for(#item, :html => { :multipart => true }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_field :description %>
<%= f.file_field :image %>
<%= f.collection_select(:categories, #categories,:id,:name)%>
<%= f.submit "Add Item", :class => "btn btn-large btn-primary" %>
<% end %>
and here's my Items Controller:
class ItemsController < ApplicationController
def new
#item = Item.new
#categories = Category.all
end
def create
#item = Item.new(params[:item])
if #item.save
#sign_in #user
flash[:success] = "You've created an item!"
redirect_to root_path
else
render 'new'
end
end
def show
end
def index
#items = Item.paginate(page: params[:page], per_page: 3)
end
end
Thanks for all of your help :)
-Rebekah
Mass Assignment usually means passing attributes into the call that creates an object as part of an attributes hash.
Try this:
#item = Item.new(name: 'item1', description: 'description1')
#item.save
#category = Category.find_by_name('category1')
#item.categories << #category
Also see:
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
http://api.rubyonrails.org/classes/ActiveModel/MassAssignmentSecurity/ClassMethods.html
I hope this helps.
IAmNaN posted the comment above that was the missing link in my code working properly. I have since written a blog post that details the process of getting the HABTM set-up. Thanks IAmNaN!
I'm writing a simple Rails model called Person that has_many :phone_numbers and I'm trying to save the phone numbers in a complex form without manually writing setter methods. accepts_nested_attributes_for should do what I want but I'm having trouble getting it to work. Here's the code I have so far:
Migration
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.string :first_name
t.string :last_name
t.integer :address_id
t.string :email
t.timestamps
end
end
def self.down
drop_table :people
end
end
class CreatePhoneNumbers < ActiveRecord::Migration
def self.up
create_table :phone_numbers do |t|
t.string :number, :limit => 10
t.string :extension, :limit => 5
t.string :description, :null => false
t.integer :telephone_id
t.string :telephone_type
t.timestamps
end
end
def self.down
drop_table :phone_numbers
end
end
Models
class Person < ActiveRecord::Base
has_one :address, :as => :addressable, :dependent => :destroy
has_many :phone_numbers,
:as => :telephone,
:dependent => :destroy
accepts_nested_attributes_for :phone_numbers
attr_protected :id
validates_presence_of :first_name, :last_name, :email
end
class PhoneNumber < ActiveRecord::Base
attr_protected :id
belongs_to :telephone, :polymorphic => true
end
View
<% form_for #person, :builder => CustomFormBuilder do |f| %>
<%= f.error_messages %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<% fields_for "person[address]", #person.address, :builder => CustomFormBuilder do |ff| %>
<%= ff.text_field :address_1 %>
<%= ff.text_field :address_2 %>
<%= ff.text_field :city %>
<%= ff.text_field :state %>
<%= ff.text_field :zip %>
<% end %>
<h2>Phone Numbers</h2>
<% #person.phone_numbers.each do |phone_number| %>
<% fields_for "person[phone_numbers][]", phone_number, :builder => CustomFormBuilder do |ff| %>
<%= ff.text_field :description %>
<%= ff.text_field :number %>
<%= ff.text_field :extension %>
<% end %>
<% end %>
<%= f.text_field :email %>
<%= f.submit 'Create' %>
<% end %>
Controller
def new
#person = Person.new
#person.build_address
#person.phone_numbers.build
respond_to { |format| format.html }
end
def create
#person = Person.new(params[:person])
respond_to do |format|
if #person.save
flash[:notice] = "#{#person.name} was successfully created."
format.html { redirect_to(#person) }
else
format.html { render :action => 'new' }
end
end
end
I have verified that a phone_numbers= method is being created, but the post still causes:
PhoneNumber(#69088460) expected, got HashWithIndifferentAccess(#32603050)
RAILS_ROOT: H:/projects/test_project
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_proxy.rb:263:in `raise_on_type_mismatch'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `replace'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `each'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations/association_collection.rb:319:in `replace'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/associations.rb:1290:in `phone_numbers='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `send'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2740:in `attributes='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `each'
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2736:in `attributes='
C:/Ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.3/lib/active_record/base.rb:2434:in `initialize'
H:/projects/salesguide/app/controllers/accounts_controller.rb:46:in `new'
H:/projects/test_project/app/controllers/accounts_controller.rb:46:in `create'
I can get this to work by manually writing the phone_numbers= method, but this would cause a tremendous duplication of effort, I would much rather learn how to do this right. Can anybody see what I'm doing wrong?
You're forgetting the to call fields_for as a method on the person form. Otherwise you're not actually using fields_for in a accept_nested_attributes_for context. Michael's solution tries to trick Rails into treating the submission as a properly defined accepts_nested_attributes_for form.
The correct syntax for what you are trying to do is:
parent_form_object.fields_for id, object_containing_values, {form_for options}, &block
You'll find the code looks cleaner and simpler to debug if you provide a symbol as id, containing the association name of the child model as defined in your Person model.
Also, the each block you're using might cause problems if #person.phone_numbers is empty. You can ensure that there is at least one set of Phone Number fields with a line similar to the one I used with
<% #phs = #person.phone_numbers.empty? ? #person.phone_numbers.build : #person.phone_numbers %>
With all corrections, this code will do what you want it to.
View
<% form_for #person, :builder => CustomFormBuilder do |f| %>
<%= f.error_messages %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<% f.fields_for :address, #person.address, :builder => CustomFormBuilder do |address_form| %>
<%= address_form.text_field :address_1 %>
<%= address_form.text_field :address_2 %>
<%= address_form.text_field :city %>
<%= address_form.text_field :state %>
<%= address_form.text_field :zip %>
<% end %>
<h2>Phone Numbers</h2>
<% #phs = #person.phone_numbers.empty? ? #person.phone_numbers.build : #person.phone_numbers %>
<% f.fields_for :phone_numbers, #phs, :builder => CustomFormBuilder do |phone_number_form| %>
<%= phone_number_form.text_field :description %>
<%= phone_number_form.text_field :number %>
<%= phone_number_form.text_field :extension %>
<% end %>
<%= f.text_field :email %>
<%= f.submit 'Create' %>
<% end %>
You might find it useful to check out the complex-form-examples repository on github for a working example. It also comes with code to dynamically add new entries for :has_many relationships from the view/form.
I was playing around with accepts_nested_attributes_for yesterday when trying to figure out Rails form with three models and namespace. I needed to setup the form slightly differently, try using: person[phone_numbers_attributes][]