Nested objects in form not validating - ruby-on-rails

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

Related

Rails Form, save nested_attributes with belongs_to association

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

Rails Client side Collection Validation fails - Simple Form

I have to build a simple app that allows users to loan and borrow books. Simply put a User can create books, and they can pick another user to loan the book to.
I have three models User, Book and Loan:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :books
has_many :loans, through: :books
has_many :borrowings, class_name: "Loan"
validates :username, uniqueness: true
validates :username, presence: true
end
class Book < ActiveRecord::Base
belongs_to :user
has_many :loans
validates :title, :author, presence: true
end
class Loan < ActiveRecord::Base
belongs_to :user
belongs_to :book
validates :user, :book, :status, presence: true
end
The LoansController looks like this:
class LoansController < ApplicationController
before_action :find_book, only: [:new, :create]
def new
#users = User.all
#loan = Loan.new
authorize #loan
end
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end
private
def loan_params
params.require(:loan).permit(:user_id)
end
def find_book
#book = Book.find(params[:book_id])
end
end
My form looks like:
<%= simple_form_for([#book, #loan]) do |f| %>
<%= f.input :user_id, collection: #users.map { |user| [user.username, user.id] }, prompt: "Select a User" %>
<%= f.submit %>
<% end %>
If I submit the form without selecting a user, and keep the "Select a User" prompt option, the form is submitted and the app crash because it can't find a user with id=
I don't know why the user presence validation in the form does not work...
you will change your Create method
def create
#loan = Loan.new
#loan.book = #book
#loan.user = User.find_by_id(loan_params[:user_id])
#loan.status = "loaned"
authorize #loan
if #loan.save
redirect_to :root
else
render :new
end
end

Rails: Including nested associative attributes in response

I have three models as follows :
#Product Model
class Product < ActiveRecord::Base
belongs_to :user
has_one :address
validates :title, :description, :user_id, presence: true
validates :product_type, numericality:{:greater_than => 0, :less_than_or_equal_to => 2}, presence: true
accepts_nested_attributes_for :address
end
#Address Model
class Address < ActiveRecord::Base
belongs_to :city
belongs_to :product
def related_city
city = address.city
end
end
#City Model
class City < ActiveRecord::Base
has_many :addresses
end
I am fetching a Product but I need to include associative attributes as well in my JSON response except few attributes.
Here is what I have done so far :
def show
product = Product.find(params[:id])
render json: product.to_json(:include => { :address => {
:include => { :city => {
:only => :name } },
},:user =>{:only=>{:first_name}}}), status: 200
end
This is giving me a syntax error. If I remove the user it is working fine but I need user's name as well in response. Moreover how would I write the above code using ruby's new hash syntax?
You can solve that problem using this gem: Active Model Serializers. It will let you create serializers for each model and use them to render the formatted JSON as you want. Take a look and let me know.
The problem is on the fifth line of your show method you have a comma surrounded by curly braces. Here is the hash sans comma, in the new syntax:
def show
product = Product.find(params[:id])
render json: product.to_json(include: { address: {
include: { city: {
only: :name }}}},
user: {only:{:first_name}}), status:200
end

How to create nested models from API request?

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

"Accepts nested attributes" not actually accepting attributes in model

I'm working on a project where there are tasks that make up a scavenger hunt. When a user creates a new hunt, I'd like the hunts/show.html.erb file to show the hunt as well as the tasks associated with that hunt. But the models are giving me trouble. I've got the hunt model setup to that it accepts nested attributes for the tasks model. So when the user creates a new hunt, she also creates three tasks automatically. I can get the new hunt to save, but I can't get those new tasks to save. Here are my models.
What's missing? Do I need an "attr accessible" statement in the HunTasks.rb file?
class Hunt < ActiveRecord::Base
has_many :hunt_tasks
has_many :tasks, :through => :hunt_tasks
accepts_nested_attributes_for :tasks, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
attr_accessible :name
validates :name, :presence => true,
:length => { :maximum => 50 } ,
:uniqueness => { :case_sensitive => false }
end
class Task < ActiveRecord::Base
has_many :hunt_tasks
has_many :hunts, :through => :hunt_tasks
attr_accessible :name
validates :name, :presence => true,
:length => { :maximum => 50 } ,
:uniqueness => { :case_sensitive => false }
end
class HuntTask < ActiveRecord::Base
belongs_to :hunt # the id for the association is in this table
belongs_to :task
end
Here's what my Hunt controller looks like:
class HuntsController < ApplicationController
def index
#title = "All Hunts"
#hunts = Hunt.paginate(:page => params[:page])
end
def show
#hunt = Hunt.find(params[:id])
#title = #hunt.name
#tasks = #hunt.tasks.paginate(:page => params[:page])
end
def new
if current_user?(nil) then
redirect_to signin_path
else
#hunt = Hunt.new
#title = "New Hunt"
3.times do
hunt = #hunt.tasks.build
end
end
end
def create
#hunt = Hunt.new(params[:hunt])
if #hunt.save
flash[:success] = "Hunt created!"
redirect_to hunts_path
else
#title = "New Hunt"
render 'new'
end
end
....
end
The major difference between your example and the railscast is that you are doing many-to-many instead of one to many (I think his was Survey had many Questions). Based on what you described, I wonder if the HuntTask model is necessary. Are the tasks for one hunt ever going to be resused in another hunt? Assuming they are, then looks like your answer is here:
Rails nested form with has_many :through, how to edit attributes of join model?
You'll have to modify your new action in the controller to do this:
hunt = #hunt.hunt_tasks.build.build_task
Then, you'll need to change your Hunt model to include:
accepts_nested_attributes_for :hunt_tasks
And modify your HuntTask model to include:
accepts_nested_attribues_for :hunt

Resources