I'm having some troubles saving nested attributes with a belongs_to association.
I have a form to pass a new Order. In it I would like the Customer to confirm some of his infos. I'm able to display the current customer infos but am unable to save new ones.
For info I'm already building the form from the current_customer, the new order is saved for the Customer no probs. What I want is to update Customers info within the form for Order
Here is my app:
#Order Model
belongs_to :customer, inverse_of: :orders, validate: false
accepts_nested_attributes_for :customer
#Customer Model
has_many :orders, inverse_of: :customer, dependent: :destroy
accepts_nested_attributes_for :orders
#Order View new
= simple_form_for #order, validate: true do |f|
= f.association :customer do |c|
= c.input :phone
= c.input :mobile
= c.input :skype
#Order controller > #Strong parameters
customer_attributes: [
:id, :phone, :mobile, :skype
]
#Order controller
def new
#order = current_customer.orders.build
render 'customers/orders/new'
end
def create
#order = current_customer.orders.build order_params
if #order.save
flash[:notice] = t('order.create.flash.success')
redirect_to customers_path
else
render 'customers/orders/new'
end
end
#Order model > Validation
This is a custom validation, since we ask the Customer to only confirm the field he choose.
%i(phone mobile skype).each do |kind|
validate "#{kind}_validable".to_sym, if: "call_kind_#{kind}?".to_sym
define_method "#{kind}_validable" do
return unless !customer.nil? && customer.send(kind).blank?
errors.add "customer.#{kind}".to_sym, I18n.t("activerecord.errors.models.customer.attributes.#{kind}.blank")
end
end
enumerize :call_kind,
in: { phone: 0, mobile: 1, skype: 2 },
predicates: { prefix: true },
default: :mobile
Thanks for reading.
My project:
Ruby On Rails 4.2.6 / Ruby 2.2.2
Devise 3.5.9
Simple form 3.1.0
Enumerize 1.1.0
Related
I can't get rails to update my nested attributes, though regular attributes work fine. This is my structure:
unit.rb:
class Unit < ApplicationRecord
has_many :unit_skill_lists
has_many :skill_lists, through: :unit_skill_lists, inverse_of: :units, autosave: true
accepts_nested_attributes_for :skill_lists, reject_if: :all_blank, allow_destroy: true
end
unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
end
skill_list.rb:
class SkillList < ApplicationRecord
has_many :unit_skill_lists
has_many :units, through: :unit_skill_lists, inverse_of: :skill_lists
end
And this is (part of) the controller:
class UnitsController < ApplicationController
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
private
def unit_params
unit_params = params.require(:unit).permit(
...
skill_list_attributes: [:id, :name, :_destroy]
)
unit_params
end
end
The relevant rows in the form (using formtastic and cocoon):
<%= label_tag :skill_lists %>
<%= f.input :skill_lists, :as => :check_boxes, collection: SkillList.where(skill_list_type: :base), class: "inline" %>
Any idea where I'm going wrong? I have tried following all guides I could find but updating does nothing for the nested attributes.
Edit after help from Vasilisa:
This is the error when I try to update a Unit:
ActiveRecord::RecordInvalid (Validation failed: Database must exist):
This is the full unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
belongs_to :database
end
There is no input field for "database". It is supposed to be set from a session variable when the unit is updated.
If you look at the server log you'll see something like skill_list_ids: [] in params hash. You don't need accepts_nested_attributes_for :skill_lists, since you don't create new SkillList on Unit create/update. Change permitted params to:
def unit_params
params.require(:unit).permit(
...
skill_list_ids: []
)
end
UPDATE
I think the best options here is to set optional parameter - belongs_to :database, optional: true. And update it in the controller manually.
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
#unit.skill_lists.update_all(database: session[:database])
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
I am following Ryan Bates railscasts video of friendly url. I am trying to implement that on my Category model by overriding the to_parammethod.
Seems like it's not working, or I am missing something.
Below is my url before overriding:
localhost:3000/search?category_id=1
After overriding the to_param the url remained same.
Following is my code:
Category model
class Category < ActiveRecord::Base
enum status: { inactive: 0, active: 1}
acts_as_nested_set
has_many :equipments, dependent: :destroy
has_many :subs_equipments, :foreign_key => "sub_category_id", :class_name => "Equipment"
has_many :wanted_equipments, dependent: :destroy
has_many :services, dependent: :destroy
validates :name, presence: true
validates_uniqueness_of :name,message: "Category with this name already exists", scope: :parent_id
scope :active, -> { where(status: 1) }
def sub_categories
Category.where(:parent_id=>self.id)
end
def to_param
"#{id} #{name}".parameterize
end
end
Controller
def search_equipments
begin
if (params.keys & ['category_id', 'sub_category', 'manufacturer', 'country', 'state', 'keyword']).present?
if params[:category_id].present?
#category = Category.active.find params[:category_id]
else
#category = Category.active.find params[:sub_category] if params[:sub_category].present?
end
#root_categories = Category.active.roots
#sub_categories = #category.children.active if params[:category_id].present?
#sub_categories ||= {}
Equipment.active.filter(params.slice(:manufacturer, :country, :state, :category_id, :sub_category, :keyword)).order("#{sort_column} #{sort_direction}, created_at desc").page(params[:page]).per(per_page_items)
else
redirect_to root_path
end
rescue Exception => e
redirect_to root_path, :notice => "Something went wrong!"
end
end
route.rb
get "/search" => 'welcome#search_equipments', as: :search_equipments
index.html.erb
The line which is generating the url
<%= search_equipments_path(:category_id => category.id ) %>
You are generating URLs in such a way as to ignore your to_param method. You're explicitly passing a value of only the ID to be used as the :category_id segment of your URLs. If you want to use your to_param-generated ID, then you need to just pass the model to the path helper:
<%= search_equipments_path(category) %>
I have a User who has many Accounts through a User_Accounts model. The User_Accounts model also tracks other information such as admin and billing access. Via the user edit form, I want to be able to edit the admin and billing boolean fields for the users current account.
user.rb
class User < ActiveRecord::Base
has_one :owned_account, class_name: 'Account', foreign_key: 'owner_id'
has_many :user_accounts
has_many :accounts, through: :user_accounts
accepts_nested_attributes_for :user_accounts
end
user_account.rb
class UserAccount < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
In the users controller, I specified which user_account I wanted to edit via the nested form and assigned to the #user_account instance variable.
users_controller.rb
def edit
#user = User.find(params[:id])
#user_account = #user.user_accounts.find_by_account_id(current_account)
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user, notice: 'User was successfully updated.'
else
render action: "edit"
end
end
private
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:admin, :billing] )
end
user/edit.html.erb
<%= f.fields_for :user_accounts, #user_account do |o| %>
<%= o.check_box :admin, class: 'checkbox' %>
<% end %>
When I submit the change, it successfully saves the user record, but doesn't update the User_account record. It appears to be passing the following:
{"name"=>"Colin 21", "email"=>"mike21#example.com", "user_accounts_attributes"=>{"0"=>{"admin"=>"1"}}}
Id is required to edit an object via accepts_nested_attributes_for. Seems that the id attribute is not allowed through strong parameters.
Try changing the user_params method in 'users_controller.rb'
def user_params
params.require(:user).permit(:name, :email, user_accounts_attributes: [:id, :admin, :billing] )
end
You need to add a hidden field for user account's id field within fields_for part of the form too.
I've a Rails API and I've two models:
class Event < ActiveRecord::Base
belongs_to :category
has_many :event_categories
has_many :events, through: :event_categories
attr_accessible :title, :description, :event_categories_attributes
accepts_nested_attributes_for :event_categories
end
and
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
attr_accessible :category_id, :event_id, :principal
validates :event, :presence => true
validates :category, :presence => true
validates_uniqueness_of :event_id, :scope => :category_id
end
In a first moment, EventCategory didn't exist so I created Event resources sending params like event[title]='event1', event[description] = 'blablbla' thought POST REST request.
My API EventsController was like this (I haven't a new method because I don't need views):
def create
#event = Event.create(params[:event])
if #event
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
This way worked correctly for me. Now, with the new EventCategory model I don't know how I could create EventCategories models at the same time.
I've trying this... but it doesn't work:
def create
#event = Event.new(params[:event])
#event.event_categories.build
if #event.save
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
Rails told me:
{
"event_categories.event": [
"can't be blank"
],
"event_categories.category": [
"can't be blank"
]
}
I send the category_id like this:
event[event_categories_attributes][0][category_id] = 2
Any ideas?
In your create action, instead of this:
#event.event_categories.build
Try this:
#event.event_categories = EventCategory.new do |ec|
ec.event = #event
ec.category = the_cattegory_you_want_to_specify
# You need both of these as you are validating the presence of event AND category
end
I have a nested form where users can book appointments. However, I've noticed an issue with the form where a user can fill out the required Client model fields and not the required Appointment model fields and the form still submits since for some reason the validation on the Appointment model isn't being triggered. The only time the Appointment validation is triggered is when the associated form fields are populated. How do I get the nested form to verify that the Appointment fields are being filled out? Since clients can have multi
Customer model:
class Customer < ActiveRecord::Base
has_many :appointments
accepts_nested_attributes_for :appointments
attr_accessible :name, :email, :appointments_attributes
validates_presence_of :name, :email
validates :email, :format => {:with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i}
validates :email, :uniqueness => true
end
Appointment model:
class Appointment < ActiveRecord::Base
belongs_to :customer
attr_accessible :date
validates_presence_of :date
end
Customers controller:
class CustomersController < ApplicationController
def new
#customer = Customer.new
#appointment = #customer.appointments.build
end
def create
#customer = Customer.find_or_initialize_by_email(params[:customer])
if #customer.save
redirect_to success_customers_path
else
# Throw error
#appointment = #customer.appointments.select{ |appointment| appointment.new_record? }.first
render :new
end
end
def success
end
end
Customers form view:
= simple_form_for #customer, :url => customers_path, :method => :post, :html => { :class => "form-horizontal" } do |customer_form|
= customer_form.input :name
= customer_form.input :email
= customer_form.simple_fields_for :appointments, #appointment do |appointment_form|
= appointment_form.input :date
UPDATE: Providing routes
resources :customers, :only => [:new, :create] do
get :success, :on => :collection
end
If a customer has to have an appointment:
class Customer < ActiveRecord::Base
has_many :appointments
accepts_nested_attributes_for :appointments
attr_accessible :name, :email, :appointments_attributes
validates_presence_of :name, :email, :appointment # <- add your appointment
....
end
This will require each customer has at least one appointment.
EDIT based on comment
Instead of using build in your controller, I think you can use create instead which will then associate that appointment with the customer and force validation.
Customers controller:
def edit
#customer = Customer.find_or_initialize_by_email(params[:customer])
#appointment = #customer.appointments.create
end
And you'd do the same in your new method