Validations misfiring in a form with multiple models - ruby-on-rails

I'm building a web app that saves a user's goals and tasks, where a user has_many goals, and a goal has_many tasks. When I try to save a goal and task together, I keep getting validation errors saying "Tasks goal can't be blank" and "Tasks content can't be blank," even though they clearly aren't. I'm certain the problem isn't with the actual form, but with the goal controller's 'new' or 'create' code, but whatever I try, I can't seem to get it right. Any ideas on why the validations for the task model are misfiring? I've been working on this issue for too long and I'm about to give up. I've included the goal controller, goal model, task model, and debug info. If you need to see any other code, let me know.
Goal Controller:
def new
#title = "New Goal"
#goal = Goal.new
#goal.tasks.build
end
def create
#user = current_user
#goal = #user.goals.build(params[:goal])
#task = #goal.tasks.build(params[:goal][:task])
if #goal.save
flash[:success] = "Goal created!"
redirect_to user_path(#user)
else
render 'new'
end
end
Goal Model:
# Table name: goals
#
# id :integer not null, primary key
# user_id :integer
# content :string(255)
# completed :boolean
# completion_date :date
# created_at :datetime
# updated_at :datetime
#
class Goal < ActiveRecord::Base
attr_accessible :content, :completed, :completion_date
belongs_to :user
has_many :tasks, :dependent => :destroy
accepts_nested_attributes_for :tasks
validates :user_id, :presence => true
validates :content, :presence => true, :length => { :maximum => 140 }
end
Task Model:
# id :integer not null, primary key
# goal_id :integer
# content :string(255)
# occur_on :date
# recur_on :string(255)
# completed :boolean
# completion_date :date
# created_at :datetime
# updated_at :datetime
#
class Task < ActiveRecord::Base
attr_accessible :content, :occur_on, :recur_on, :completed
belongs_to :goal
validates :goal_id, :presence => true
validates :content, :presence => true, :length => { :maximum => 140 }
end
Debug Dump after an unsuccessful save:
--- !map:ActiveSupport::HashWithIndifferentAccess
utf8: "\xE2\x9C\x93"
authenticity_token: NF/vVwinKQlGAvBwEnlVX/d9Wvo19MipOkYb7qiElz0=
goal: !map:ActiveSupport::HashWithIndifferentAccess
content: some goal
tasks_attributes: !map:ActiveSupport::HashWithIndifferentAccess
"0": !map:ActiveSupport::HashWithIndifferentAccess
content: some task
commit: Submit
action: create
controller: goals

This is a problem with nested attributes. You cannot validate the presence of the encapsulating model from the nested model (in your case, you cannot validate the presence of goal_id from Task). When validations are run the goal is not yet saved, and thus has no id, so it is impossible to assign it.
You can either eliminate the validation that is causing the problem, or not use the built-in nested attributes. In the latter case, you would need to add your own logic to first save the goal and then create any nested tasks.
Bummer, huh? I wish someone would come up with a good solution for this...maybe I'll work on it if I ever get some free time.

Related

ensure fields hold values as more fields are added rails

I have a form for JobDeliveryCost where a user can add delivery costs. Every time the user adds one, an additional field is created to add another. at the moment, I have a form and I display the fields.
-jdc_array=(#job.job_delivery_costs.any? ? [#job.job_delivery_costs,#new_delivery].flatten : [#new_delivery])
-jdc_array.each do |jdc|
= simple_form_for [:admin, #job, #new_delivery] do |f|
%tr
%td
= jdc.timing
= f.input :timing, collection: JobDeliveryCost::TIMINGS, :include_blank => "please select"
%td
= f.input :delivery_cost_id, collection: DeliveryCost.order(:title), :label_method => :title,:value_method => :id
%td
-if jdc.new_record?
=f.submit "add"
-else
%td
= jdc.cost_per_unit
= f.input :cost_per_unit
%td
= jdc.quantity
= f.input :quantity
Instead of displaying the inputted value above each form entry, how do I get the fields to hold their value instead?
Also, how would I display the value of this
= f.input :delivery_cost_id, collection: DeliveryCost.order(:title), :label_method => :title,:value_method => :id
as it is a child attribute of the DeliveryCost model?
For extra enfo I have added my controller and relevant models
class Admin::JobDeliveryCostsController < ApplicationController
before_action :set_job
def index
# raise #job.inspect
if get_deliverable
#jdc_array=(#job.job_delivery_costs.any? ? [#job.job_delivery_costs,#new_delivery] : [#new_delivery])
# raise #jdc_array.inspect
#new_delivery = #deliverable.job_delivery_costs.build
end
set_job_delivery_cost
end
def create
if #job
#job_delivery_cost = JobDeliveryCost.new(job_delivery_cost_params)
#job_delivery_cost.job = #job
if #job_delivery_cost.quantity.nil?
#job_delivery_cost.quantity = 1
end
# raise #job_delivery_cost.inspect
if #job_delivery_cost.save
flash[:success] = "Delivery Costs Added"
else
flash[:error] = "Delivery Costs not Added"
end
else
flash[:error] = "Couldn't find the Job."
end
redirect_to admin_job_job_delivery_costs_path(#job)
end
def destroy
set_job_delivery_cost
if #job.present? && #job_delivery_cost.present?
#job_delivery_cost.destroy
flash[:success] = "Job delivery cost removed"
else
flash[:error] = "Couldn't find the record"
end
redirect_to admin_job_job_products_path(#job)
end
private
def set_job
#job = Job.find_by(id: params[:job_id])
end
def set_job_delivery_cost
#job_delivery_cost ||= JobDeliveryCost.find_by(id: params[:id])
end
def job_delivery_cost_params
params.require(:job_delivery_cost).permit!
end
def get_deliverable
return #deliverable if #deliverable
if params[:contact_id].present?
#deliverable = Contact.find_by(id: params[:contact_id])
elsif params[:client_id].present?
#deliverable = Client.find_by(id: params[:client_id])
elsif params[:job_id].present?
#deliverable = Job.find_by(id: params[:job_id])
end
#deliverable
end
end
delivery_cost.rb
# == Schema Information
#
# Table name: delivery_costs
#
# id :integer not null, primary key
# title :string(255)
# unit :string(255)
# cost_per_unit :float
# created_at :datetime
# updated_at :datetime
#
class DeliveryCost < ActiveRecord::Base
UNIT_DAY='day'
UNIT_HOUR='hour'
UNIT_MILE='mile'
UNITS=[UNIT_DAY,UNIT_HOUR,UNIT_MILE]
has_many :job_delivery_costs
has_many :jobs, through: :job_delivery_costs
validates :cost_per_unit, presence: true
validates :unit, inclusion: UNITS
validates :title, presence: true
before_destroy :survive_if_jobs
private
def survive_if_jobs
jobs.empty?
end
end
JobDeliveryCost
# == Schema Information
#
# Table name: job_delivery_costs
#
# id :integer not null, primary key
# job_id :integer
# delivery_cost_id :integer
# cost_per_unit :float
# quantity :integer
# timing :string(255)
# created_at :datetime
# updated_at :datetime
#
class JobDeliveryCost < ActiveRecord::Base
TIMING_INSTALL='install'
TIMING_BREAKDOWN='breakdown'
TIMINGS=[TIMING_INSTALL,TIMING_BREAKDOWN]
belongs_to :delivery_cost
belongs_to :job
validates :quantity, presence: true, numericality: {greater_than_or_equal_to:1}
validates :timing, inclusion: TIMINGS
#validates :cost_per_unit, presence: true
validate :validate_cost_per_unit
validate :check_associates
# validate :quantity_default
before_save :init
private
def check_associates
associated_object_exists DeliveryCost, :delivery_cost_id
associated_object_exists Job, :job_id
end
def validate_cost_per_unit
if delivery_cost and cost_per_unit.blank?
self.cost_per_unit=delivery_cost.cost_per_unit
end
return false if cost_per_unit.blank?
end
def init
if self.quantity.nil?
self.quantity = 1
end
end
end
If I understand correctly, you are going to want to use javascript to solve your problem. There is an excellent railscast that discusses nested model forms but it also shows you how to build javascript for dynamically adding field elements with links.
Instead of links, you will most likely want to register an onChange event (meaning the user changed data in the field) to add another field.

can't convert symbol into integer error when saving user

So here is my issue. I have been working with users I created at the beginning of my project for a month now. Today I switched from sqllite to sqlserver to meet client requirements and when I went to use my registration form to create a new user I got the following error:
can't convert Symbol into Integer
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"51nF50CYGNqz3N4o7TUYSyWeTadulXojQBPqERjvlcY=",
"user"=>{
"email"=>"test#blizzardlabs.com",
"login"=>"bgarrison",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"profile_attributes"=>{
"prefix"=>"",
"first_name"=>"Bill",
"last_name"=>"Garrison",
"suffix"=>"",
"birthday"=>"1983-06-01",
"phone_numbers_attributes"=>{
"0"=>{
"info"=>"1234567890",
"label"=>"Cell"
}
}
}
},
"commit"=>"Register"}
I have a feeling that at some point I messed up the registration process but I can't for the life of me figure out where. User-> has_one profile-> has_many phone_numbers.
User Controller:
def create
#user = User.new(params[:user])
if #user.save
#profile = #user.profile
flash[:notice] = "Your account has been created."
redirect_to(#user)
else
flash[:notice] = "There was a problem creating you."
render :action => :new, :layout => 'logged_out'
end
end
User Model:
class User < ActiveRecord::Base
# Accessible attributes
attr_accessible :login,
:email,
:password,
:password_confirmation,
:profile_attributes,
:active
# Associations
has_one :profile, dependent: :destroy, autosave: true
# Allows for a profile hash in user creation (stored in :profile_attributes)
accepts_nested_attributes_for :profile
Profile Model:
class Profile < ActiveRecord::Base
# Accessible Attributes
attr_accessible :birthday,
:company_id,
:first_name,
:last_name,
:prefix,
:suffix,
:phone_numbers_attributes,
:addresses_attributes
# Model Associations
has_many :phone_numbers, :as => :contactable, :class_name => "PhoneNumber", autosave: true
accepts_nested_attributes_for :phone_numbers, allow_destroy: true, reject_if: :all_blan
Any help would be appreciated. Thanks!
Update:1 Also, I have tested some and realized if I leave out the phone number then it works.....if I then update using the same form and add a phone number everything works fine.
Nested attributes should be passed in as Array:
"user"=>{
"email"=>"test#blizzardlabs.com",
"login"=>"bgarrison",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]",
"profile_attributes"=>[
{
"prefix"=>"",
"first_name"=>"Bill",
"last_name"=>"Garrison",
"suffix"=>"",
"birthday"=>"1983-06-01",
"phone_numbers_attributes"=>{
"0"=>{
"info"=>"1234567890",
"label"=>"Cell"
}
}
}
]
}
So, after a couple days of banging my head against the wall I have finally figured this out. However to understand it I need to explain my model's a bit better.
Basically, from above you can see that a User has a profile which has many phone_numbers and addresses through a polymorphic association (:as => :contactable ). However, contactable is actually a base class called ContactInformation which uses STI to contain contactable information of all types.
At one point I decided that having 4 extra fields for addresses was cluttering up the STI relationship but I still wanted to keep it. My solution was to serialize all those fields into the "info" field of ContactInformation. Right now, phone numbers only have "number" as a field that is serialized and stored into "info" but if I ever want to seperate it out into "area code" "extension" etc the implementation will be simple.
This leads to the problem. On my registration form I was using label / info for my phone_number fields instead of label / number. I had edited my edit form but not my new form (yes i know they should be the same one but I have a special ajax form for editing).
Here is the code for ContactInformation / PhoneNumber / Address
class ContactInformation < ActiveRecord::Base
attr_accessible :contactable_id, :contactable_type, :info, :label, :type
belongs_to :contactable, :polymorphic => true
end
class PhoneNumber < ContactInformation
attr_accessible :number
stash :number, in: :info
#----------------------------------Validations--Start-------------------------
validates :number, presence: true
#----------------------------------Validations--End---------------------------
end
class Address < ContactInformation
attr_accessible :street_address, :city, :state, :postal
stash :street_address, :city, :state, :postal, in: :info
#----------------------------------Validations--Start-------------------------
validates :street_address, :city, :state, :postal, presence: true
#----------------------------------Validations--End---------------------------
end

Limit the number of rows of a column a user has based on parameter

I'm building a call-tracking application as a way to learn rails and twilio.
Right now, I have the model scheme plans has_many users has_many phones.
In the plans model, I have a parameter called max_phone_numbers.
What I'd like to do is to limit the number of phones a user has based on the max_phone_numbers the plan gives.
The flow looks something like this :
1) User buys a bunch of phone numbers
2)When User.phones.count = max_phone numbers, then ability to buy more phone numbers is disabled, and a link pops up to the upgrade_path
I'm not quite sure how I would go about doing this though. What are the combinations of things I would need to do in my model, and in my controller?
What would I define in my controller, in such a way that in the view I can warp if/then statements around the buttons?
i.e if limit is reached, than show this, else show button
What would I put in my models to prevent someone from just visiting the link instead?
Any guidance, or resources on doing something like this would be greatly appreciated
Here's my current user model
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# password_digest :string(255)
# remember_token :string(255)
# twilio_account_sid :string(255)
# twilio_auth_token :string(255)
# plan_id :integer
# stripe_customer_token :string(255)
#
# Twilio authentication credentials
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :plan_id, :stripe_card_token
has_secure_password
belongs_to :plan
has_many :phones, dependent: :destroy
before_save { |user| user.email = email.downcase }
before_save :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: true
validates :password, presence: true, length: { minimum: 6 }, on: :create
validates :password_confirmation, presence: true, on: :create
validates_presence_of :plan_id
attr_accessor :stripe_card_token
def save_with_payment
if valid?
customer = Stripe::Customer.create(description: email, plan: plan_id, card: stripe_card_token)
self.stripe_customer_token = customer.id
save!
end
rescue Stripe::InvalidRequestError => e
logger.error "Stripe error while creating customer: #{e.message}"
errors.add :base, "There was a problem with your credit card."
false
end
def create_twilio_subaccount
#client = Twilio::REST::Client.new(TWILIO_PARENT_ACCOUNT_SID, TWILIO_PARENT_ACCOUNT_TOKEN)
#subaccount = #client.accounts.create({:FriendlyName => self[:email]})
self.twilio_account_sid = #subaccount.sid
self.twilio_auth_token = #subaccount.auth_token
save!
end
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
You could add a custom validation to your Phone model to check if a user has reached their limit. That would prevent any new Phone's from being created if the user has reached their limit.
In your User class
def at_max_phone_limit?
self.phones.count >= self.plan.max_phone_numbers
end
In your Phone class
validate :check_phone_limit, :on => :create
def check_phone_limit
if User.find(self.user_id).at_max_phone_limit?
self.errors[:base] << "Cannot add any more phones"
end
end
In your view/form, you would do something like this
<% if #user.at_max_phone_limit? %>
<%= link_to "Upgrade your Plan", upgrade_plan_path %>
<% else %>
# Render form/widget/control for adding a phone number
<% end %>

Same Issue in Two Tests: Something Wrong With Events

I believe something is wrong with the creation of events in my testing environment.
When I navigate in the browser everything is fine.
The two errors I get are:
1) Error:
test_should_post_save_period(PeriodRegistrationsControllerTest):
NoMethodError: undefined method `event' for nil:NilClass
2) Error:
test_should_get_index(PeriodsControllerTest):
ActionView::Template::Error: undefined method `name' for nil:NilClass
Error 1 test:
def setup
#period_registration= FactoryGirl.create(:period_registration)
end
test "should post save_period" do
sign_in(FactoryGirl.create(:user))
assert_difference('PeriodRegistration.count') do
post :save_period, period_registration: FactoryGirl.attributes_for(:period_registration)
end
assert_not_nil assigns(:period_registration)
# assert_response :success
end
Error 2 test:
test "should get index" do
sign_in(FactoryGirl.create(:user, admin: true))
get :index
assert_not_nil assigns(:periods)
assert_response :success
end
Error number one corresponds with this action in the controller:
def save_period
#period_registration = PeriodRegistration.new(params[:registration])
#period_registration.save
flash[:success] = "Successfully Registered for Session."
redirect_to event_url(#period_registration.period.event) #problem line
end
The second error corresponds with this line in my view:
<h6><%= period.event.name %> in <%= period.event.city %>, <%= period.event.state%></h6>
Here is my event factory:
factory :event do
name 'First Event'
street '123 street'
city 'Chicago'
state 'Iowa'
date Date.today
end
factory :period do
name 'First Period'
description 'This is a description'
start_time Time.now + 10.days
end_time Time.now + 10.days + 2.hours
event
product
end
factory :period_registration do
user
period
end
And my event model looks like this:
# == Schema Information
#
# Table name: events
#
# id :integer not null, primary key
# name :string(255)
# date :date
# street :string(255)
# city :string(255)
# state :string(255)
# created_at :datetime not null
# updated_at :datetime not null
#
class Event < ActiveRecord::Base
attr_accessible :city, :date, :name, :state, :street
has_many :periods
validates :name, presence: true
validates :street, presence: true
validates :city, presence: true
validates :state, presence: true
end
and here is my period model:
# == Schema Information
#
# Table name: periods
#
# id :integer not null, primary key
# name :string(255)
# event_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# start_time :time
# end_time :time
# description :text
# product_id :integer
#
class Period < ActiveRecord::Base
attr_accessible :event_id, :name, :time, :start_time, :end_time, :description, :product_id
belongs_to :event
belongs_to :product
has_many :period_registrations
validates_time :end_time
validates_time :start_time
validates_presence_of :name
validates_presence_of :start_time
validates_presence_of :end_time
validates_presence_of :description
end
Any ideas on what could be causing this?
I think it's because FactoryGirl.attributes_for(:period_registration) returns {} (empty hash). You can check it in rails console. And also you have typo in code: in test you send period_registration: FactoryGirl.attributes_for(:period_registration), but in controller you expects params[:registration]. This leads to the empty PeriodRegistration model is created in db. This model does not contain event_id and when you request event from model, it returns nil.
Why you do not use mock for these kind of tests?
I think it's because you are missing your Factory for the User model

Why am I getting a NoMethodError for an attribute that exists in my model?

This is the error I get:
ContactPostalcardsController#skip (NoMethodError) "undefined method `status=' for #<ContactPostalcard:0x2b21433d64b0>"
This is the code calling it and trying to assign a value to the status attribute for ContactPostalcard (the Model):
def skip
#contact_postalcard = ContactPostalcard.new(params[:contact_postalcard])
#contact_postalcard.contact_id = params[:contact_id]
#contact_postalcard.postalcard_id = params[:postalcard_id]
#contact_postalcard.status = "skipped"
#contact_postalcard.date_sent = Date.today
#contact_postalcard.date_created = Date.today
if #contact_postalcard.save
render :text => 'This email was skipped!'
end
end
This is the Model referred. Note the "annotate" output shows status as an attribute:
class ContactPostalcard < ActiveRecord::Base
attr_accessible :title, :contact_id, :postal_id, :postalcard_id, :message, :campaign_id, :date_sent, :status
belongs_to :contact
belongs_to :postalcard
alias_attribute :body, :message
alias_attribute :subject, :title
named_scope :nosugar, :conditions => { :sugarcrm => false }
def company_name
contact = Contact.find_by_id(self.contact_id)
return contact.company_name
end
def asset
Postalcard.find_by_id(self.postalcard_id)
end
def asset_class
Postalcard.find_by_id(self.postalcard_id).class.name
end
end
# == Schema Information
#
# Table name: contact_postalcards
#
# id :integer not null, primary key
# title :string(255)
# contact_id :integer
# postalcard_id :integer
# message :text
# campaign_id :integer
# date_sent :datetime
# created_at :datetime
# updated_at :datetime
# postal_id :integer
# sugarcrm :boolean default(FALSE)
# status :string(255)
#
I am unclear as to why I keep getting an 'undefined method' -- I have added the status attribute (it had been missing before but used a migration and then raked), so need some help...thank you.
Have you restarted your Rails application since you ran your migration? If you're running in production mode, Rails caches your classes until you restart it, and since status wasn't an attribute before the migration, Rails wouldn't have added accessor methods for it, which would explain why status= is undefined.

Resources