Nested model, update_attributes not working - ruby-on-rails

I am having a hard time with what I thought would be an textbook update example. Searched SO but couldn't find the answer. In short, when I click submit the user_id in the profile model gets wiped out and no other data gets saved. I am using Rails 3.2.2.
Here is what I have...
User model...
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :profile_attributes
has_one :profile
accepts_nested_attributes_for :profile
end
Profile model...
class Profile < ActiveRecord::Base
validates :first_name, :presence => true
validates :last_name, :presence => true
belongs_to :user
attr_accessible :first_name, :last_name
end
Users controller...
class UsersController < ApplicationController
def new
#user = User.new
#user.accounts_users.build()
#user.build_profile()
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'Profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
end
Nested form...
<%= form_for #user, :validate => true do |f| %>
<%= f.fields_for :profile do |p| %>
<fieldset>
<div class="field">
<%= p.label :first_name %>
<%= p.text_field :first_name %>
</div>
<div class="field">
<%= p.label :last_name %>
<%= p.text_field :last_name %>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Edit Profile', :class => "btn btn-large btn-success" %>
<%= cancel %>
</div>
</fieldset>
<% end %>
Edit: I edited the UsersController to include the new action. Why would the new action affect the edit/update actions?

I had a similar problem before. Can we see the code for your new action? Do you have #user.build_profile in there (right beneath #user = User.new)? Or is the new action working fine?

Related

How to pass an account_id through a nested form in Rails

I'm trying to set up a nested form in rails and both the parent and child objects in the form need to have an "Account ID" so that they are both scoped to the current user's account, but I can't figure out how to pass the current user's Account ID for the child object through the nested form. I keep getting a validation error of "Account id must be present" for the nested object.
The parent form is "Product" and I'm trying to nest "Options" into the Product.new form.
I'm trying to do something like this:
#product.options.account_id = current_user.account.id
But it's not working.
Here is the Product model:
class Product < ApplicationRecord
belongs_to :account
has_many :options, dependent: :destroy
accepts_nested_attributes_for :options, allow_destroy: true
validates :account_id, presence: true
validates :name, presence: true, length: { maximum: 120 }
end
And options model:
class Option < ApplicationRecord
belongs_to :account
belongs_to :product
has_many :option_values, dependent: :destroy
validates :account_id, presence: true
validates :name, presence: true,
length: { maximum: 60 }
end
Here's how I'm nesting "Options" into the Product form:
<%= form.fields_for :options do |builder| %>
<fieldset class='form-group'>
<%= builder.label :name, 'Add option(s)' %>
<%= builder.text_field :name %>
<small id="optionHelp" class="form-text text-muted">
(e.g. "Sizes" or "Color")
</small>
</fieldset>
<% end %>
And here is my ProductsController:
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
before_action :restrict_access
def index
#products = Product.where(:account_id => current_user.account.id).all
end
def show
end
def new
#product = Product.new
#product.options.build
end
def edit
end
def create
#account = current_user.account
#product = #account.products.build(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
end
end
private
def set_product
if Product.find(params[:id]).account_id == current_user.account.id
#product = Product.find(params[:id])
else
redirect_to dashboard_path
end
end
def restrict_access
if index
authorize #products
else
authorize #product
end
end
def product_params
params.require(:product).permit(:account_id, :name,
options_attributes: [:id, :account_id, :name ])
end
end
What is the correct way to do this?
Alternatively, you can pass a hidden_field with form and nested_form as given below: -
<%= form_for #product do |form|%>
<%= form.fields_for :options do |builder| %>
<fieldset class='form-group'>
<%= builder.label :name, 'Add option(s)' %>
<%= builder.text_field :name %>
<small id="optionHelp" class="form-text text-muted">
(e.g. "Sizes" or "Color")
</small>
<%=builder.hidden_field :account_id, value: current_user.account.id%>
</fieldset>
<% end %>
<%= form.hidden_field :account_id, value: current_user.account.id%>
<%end%>
Other than this you can set account_id at the controller
def new
##product = Product.new
#product = current_user.account.products.new
#product.options.build(account_id: current_user.account.id)
end
The easiest option would be to add a hidden field into both forms:
https://apidock.com/rails/ActionView/Helpers/FormHelper/hidden_field
So in your case, for the options form, something like:
<%= form.fields_for :options do |builder| %>
<fieldset class='form-group'>
<%= builder.label :name, 'Add option(s)' %>
<%= builder.hidden_field :account_id, value: current_account.id %>
<%= builder.text_field :name %>
<small id="optionHelp" class="form-text text-muted">
(e.g. "Sizes" or "Color")
</small>
</fieldset>
<% end %>
This will give you access to the [:option][:account_id] param, which will match the current user's.

Nested attributes not being saved in rails form

I'm trying to create a nested form in Ruby on Rails. The form appears as expected. But when it is saved the nested attribute, Booking, is not saved.
cleaner.rb
class Cleaner < ActiveRecord::Base
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :quality_score
validates :quality_score, inclusion: 0.0...5.0
has_many :assignments
has_many :bookings
has_many :cities, through: :assignments
has_many :customers, through: :bookings
end
customer.rb
class Customer < ActiveRecord::Base
validates_presence_of :first_name
validates_presence_of :last_name
validates_uniqueness_of :phone_number
belongs_to :city
has_many :bookings
has_many :cleaners, through: :bookings
accepts_nested_attributes_for :bookings
end
booking.rb
class Booking < ActiveRecord::Base
belongs_to :customer
belongs_to :cleaner
validates_presence_of :customer
validates_presence_of :cleaner
validates_presence_of :date
end
customers_controller.rb
class CustomersController < ApplicationController
before_action :set_customer, only: %i[show edit update destroy]
def index
#customers = Customer.all
end
def show; end
def new
#customer = Customer.new
end
def edit; end
def create
#customer = Customer.find_or_initialize_by(phone_number: params[:phone_number])
#customer.assign_attributes(customer_params)
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render :show, status: :created, location: #customer }
else
format.html { render :new }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #customer.update(customer_params)
format.html { redirect_to #customer, notice: 'Customer was successfully updated.' }
format.json { render :show, status: :ok, location: #customer }
else
format.html { render :edit }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
def destroy
#customer.destroy
respond_to do |format|
format.html { redirect_to customers_url, notice: 'Customer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_customer
#customer = Customer.find(params[:id])
end
def customer_params
params.require(:customer).permit(:first_name, :last_name, :phone_number, :city, :city_id, bookings_attributes: %i[cleaner_id date])
end
end
HomeController.rb
class HomeController < ApplicationController
def index
#customer = Customer.new
#customer.bookings.build
end
end
Parameters
{"utf8"=>"✓", "authenticity_token"=>"c4xo2M4r57+/xBsmcc+7yajpQU13u1kiwmOthx/nP7HiJXJIfS9/OqC0MrWCcaDrSW/xN8UGk2+LVfnUnbTb3A==", "customer"=>{"first_name"=>"adfad", "last_name"=>"fad", "phone_number"=>"9392323", "city_id"=>"1", "bookings_attributes"=>{"0"=>{"cleaner_id"=>"1"}}}, "#<ActionView::Helpers::FormBuilder:0x007f86bec96480>"=>{"date(1i)"=>"2017", "date(2i)"=>"8", "date(3i)"=>"2"}, "commit"=>"Create Customer"}
updated form
<h1>Sign Up Now</h1>
<%= form_for #customer do |f| %>
<% if #customer.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#customer.errors.count, "error") %> prohibited this customer from being saved:</h2>
<ul>
<% #customer.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :first_name %><br>
<%= f.text_field :first_name %>
</div>
<div class="field">
<%= f.label :last_name %><br>
<%= f.text_field :last_name %>
</div>
<div class="field">
<%= f.label :phone_number %><br>
<%= f.text_field :phone_number %>
</div>
<div class="field">
<%= f.label :city %><br>
<%= f.select :city_id, options_for_select(City.pluck(:name, :id)) %>
</div>
<%= f.fields_for :bookings do |b| %>
<%= b.date_select :date %>
<br>
<%= b.select :cleaner_id, Cleaner.all.pluck(:first_name, :id) %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
It's now failing with 2 errors
Bookings customer can't be blank
Bookings date can't be blank
use the iteration of the first #customer so rails can read properly
i saw this example https://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
<%= f.fields_for :booking do |ff| %>
<%= ff.select :city_id,options_for_select(City.pluck(:name, :id)) %>
i hope it works cross finger :)

Rails Paperclip Gem Saving Multiple Attachments per Model instance

I'm very new to Rails development and having a problem saving multiple images/attachments to a model. My problem is that the code below is not actually saving to the item_images table when I submit the form. I am following This Article as a guide, though it seems to be a bit out of date. I feel I'm in a little over my head at this point so I hope someone can point out what I'm missing. Thanks!
I have the following models:
item.rb
class Item < ActiveRecord::Base
has_many :item_images, :dependent => :destroy
accepts_nested_attributes_for :item_images, :reject_if => lambda { |t| t['item_image'].nil? }
end
item_image.rb
class ItemImage < ActiveRecord::Base
belongs_to :item
has_attached_file :image,
:styles => { thumb: "100x100#", small: "400x400#", large: "700x700" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
My controller looks like this:
items_controller.rb
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
# GET /items
# GET /items.json
def index
#items = Item.all
end
# GET /items/1
# GET /items/1.json
def show
end
# GET /items/new
def new
#item = Item.new
4.times {#item.item_images.build}
end
# GET /items/1/edit
def edit
4.times {#item.item_images.build}
end
# POST /items
# POST /items.json
def create
#item = Item.new(item_params)
respond_to do |format|
if #item.save
format.html { redirect_to #item, notice: 'Item was successfully created.' }
format.json { render :show, status: :created, location: #item }
else
format.html { render :new }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /items/1
# PATCH/PUT /items/1.json
def update
respond_to do |format|
if #item.update(item_params)
format.html { redirect_to #item, notice: 'Item was successfully updated.' }
format.json { render :show, status: :ok, location: #item }
else
format.html { render :edit }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
# DELETE /items/1
# DELETE /items/1.json
def destroy
#item.destroy
respond_to do |format|
format.html { redirect_to items_url, notice: 'Item was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_item
#item = Item.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def item_params
params.require(:item).permit(:title, :description, :price, :available, :sort_shop, :sort_gallery, :item_type, :size)
end
end
form.html.erb
<%= form_for #item, html: { multipart: true } do |f| %>
<% if #item.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#item.errors.count, "error") %> prohibited this item from being saved:</h2>
<ul>
<% #item.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price %>
</div>
<div class="field">
<%= f.label :available %><br>
<%= f.check_box :available %>
</div>
<div class="field">
<%= f.label :sort_shop %><br>
<%= f.number_field :sort_shop %>
</div>
<div class="field">
<%= f.label :sort_gallery %><br>
<%= f.number_field :sort_gallery %>
</div>
<div class="field">
<%= f.label :item_type %><br>
<%= f.text_field :item_type %>
</div>
<div class="field">
<%= f.label :size %><br>
<%= f.text_field :size %>
</div>
<%= f.fields_for :item_images do |builder| %>
<% if builder.object.new_record? %>
<div class="field">
<%= builder.label :image, "Image File" %>
<%= builder.file_field :image %>
</div>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Try this in strong parameters in Items controller
params.require(:item).permit(:title, :description, :price, :available, :sort_shop, :sort_gallery, :item_type, :size,item_images_attributes: [:image ])
than in ItemImage.rb add this line
belongs_to :item, optional: true,
and remove this line from Item.rb
:reject_if => lambda { |t| t['item_image'].nil? }
`
If you get any error please reply

Rails Association, undefined method

I'm working on two simple model
class Permit < ApplicationRecord
belongs_to :user, optional: true
validates :user_id, presence: true
end
class User < ApplicationRecord
has_many :permits
has_secure_password
end
In rail console, I am doing the following
user = User.find(10)
Which assign the a User object to user.
But when I want to create a permit for the user it gives this error
NoMethodError in PermitsController#create
undefined method `Permit' for #<User:0xaaa3528>
How to solve this problem? Thanks!
This is my permit_controller which the problem I believe is in CREATE action
class PermitsController < ApplicationController
before_action :set_permit, only: [:show, :destroy]
def index
#permits = Permit.all
end
def new
#permits = Permit.new
end
def create
#permits = user.permits.create(permit_params)
if #permits.save
redirect_to #permits
else
redirect_to contact_path
end
end
def destroy
Permit.destroy_all(user_id: 1)
respond_to do |format|
format.html { redirect_to users_url, notice: 'Permit was successfully canceled.' }
format.json { head :no_content }
end
end
def show
#permits = Permit.find(params[:id])
end
def update
respond_to do |format|
if #permits.update(user_params)
format.html { redirect_to #user, notice: 'Permit was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_permit
#permits = Permit.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def permit_params
params.require(:permit).permit(:vehicle_type, :name, :studentid, :department, :carplate, :duration, :permitstart, :permitend)
end
end
Permit/new/html.erb
<% provide(:title, 'New Permit') %>
<h1>Permit Application</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(#permits) do |f| %>
<%= f.label :"Vehicle" %>
<%= f.text_field :vehicle_type, class: 'form-control' %>
<%= f
.label :"License Plate" %>
<%= f.text_field :carplate, class: 'form-control' %>
<%= f.label :"Student ID" %>
<%= f.text_field :studentid, class: 'form-control' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :"Department of applicant" %>
<%= f.text_field :department, class: 'form-control' %>
<%= f.label :permit_start %>
<%= f.date_select :permitstart, class: 'form-control' %>
<%= f.label :permit_end %>
<%= f.date_select :permitend, class: 'form-control' %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
You have to create the permit as follows:
user.permits.create(..)
You set before_action :set_permit, only: [:show, :destroy], that mean you can only use #permits (should be singular) in those 2 methods, I see you use #permits in update. Didn't see you permit user_id for permit_params.
In create #permits = user.permits.create(permit_params) you didn't declare user.
You set before_action :set_permit, only: [:show, :destroy], so why don't #permits.destroy
belongs_to :user, optional: true
validates :user_id, presence: true
optional: true but user_id with presence: true, isn't it conflict? how do you require an optional?
You have more bugs to deal thank you thought.
Good luck
I'm not a rails expert but i'll try to help out with this and try to work out the possible places of errors.
In your schema ensure that in your Permits table you have a user_id column.
Test out that the association works in rails console. user = User.first
user.permits
If it works, great it means your association is working.
Test your permits create function on its own first without including "User" in it. Add the user aspect in only after your permits is fully functioning!
Hope it helps! Cheers

Linking two models in a multi-model form

I have a nested multimodel form right now, using Users and Profiles.
Users has_one profile, and Profile belongs_to Users.
When the form is submitted, a new user is created, and a new profile is created, but they are not linked (this is the first obvious issue). The user's model has a profile_id row, and the profile's model has a user_id row.
Here is the code for the form:
<%= form_for(#user, :url => teams_path) do |f| %>
<p><%= f.label :email %><br />
<%= f.text_field :email %></p>
<p><%= f.label :password %><br />
<%= f.password_field :password %></p>
<p><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></p>
<%= f.hidden_field :role_id, :value => #role.id %></p>
<%= f.hidden_field :company_id, :value => current_user.company_id %></p>
<%= fields_for #user.profile do |profile_fields| %>
<div class="field">
<%= profile_fields.label :first_name %><br />
<%= profile_fields.text_field :first_name %>
</div>
<div class="field">
<%= profile_fields.label :last_name %><br />
<%= profile_fields.text_field :last_name %>
</div>
<% end %>
<p><%= f.submit "Sign up" %></p>
<% end %>
A second issue, is even though the username, and password are successfully created through the form for the user model, the hidden fields (role_id & company_id - which are also links to other models) are not created (even though they are part of the model) - the values are successfully shown in the HTML for those fields however.
Any help would be great!
As requested, the controller code:
def new
#user = User.new
#user.profile = Profile.new
#role = Role.find_by_name("Regular")
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #teams }
end
end
def create
#user = User.new(params[:user])
#profile = Profile.new(params[:profile])
respond_to do |format|
if #profile.save && #user.save
format.html { redirect_to (teams_path) }
format.xml { render :xml => #profile, :status => :created, :location => #profile }
else
format.html { render :action => "new" }
format.xml { render :xml => #profile.errors, :status => :unprocessable_entity }
end
end
end
To answer question number one, change the following:
#profile = Profile.new(params[:profile])
to
#profile = #user.profile.build(params[:profile]) #In the case of a has_many relationship
or
#profile = #user.build_profile(params[:profile]) #In the case of a has_one relationship
The build command builds a new profile with the user_id properly set.
For the second question, can you delete the query for Role and Company during the new action and instead assign those during the create action? This would remove the necessity of passing hidden parameters.

Resources