Rails: Strange validation error - ruby-on-rails

I have a Location model with fields:
Location(id: integer, name: string, address: string, latitude: decimal, longitude: decimal, created_at: datetime, updated_at: datetime, rating: decimal, rating_count: integer)
In my location model, i have the following validations (which work fine):
attr_accessible :name, :address, :latitude, :longitude, :rating, :rating_count
validates :name, :presence => true, :length => { :maximum => 50 }
validates :address, :presence => true
validates :rating, :inclusion => 0..5
validates :rating, :presence => { :message => " cannot be blank" }
I also have a Post model with fields:
Post(id: integer, user_id: integer, location_id: integer, info: string, created_at: datetime, updated_at: date time)
And the following validations:
attr_accessible :info, :address, :name, :rating
belongs_to :user
belongs_to :location
attr_accessor :rating, :address, :name #Virtual attributes for the Post create form
validates :name, :presence => true, :length => { :maximum => 50 }
validates :address, :presence => true
validates :rating, :inclusion => 0..5
validates :rating, :presence => { :message => " cannot be blank" }
Now, the issue is that when I try to create a new Post, the validations for name and address work fine (just as they work for Location), but the validation for rating always fails. Even when I enter a rating of '3', I get the error Rating is not included in the list for some reason, although a rating of '3' is validated correctly if used directly with the Location#create action.
Does anyone know why only the rating validation isn't working as expected with Post, even though it works fine with Location, and the other validations behave identically with the two models ?
EDIT: Here's the create action from posts_controller:
def create
#post = current_user.posts.build(params[:post])
#location = Location.find_by_address_and_name(params[:post][:address], params[:post][:name])
if #location.nil?
#location = Location.find_by_address(params[:post][:address])
end
#If the address and the name of the location is the same, just update the rating for that location, and associate the post with the location
if (#location && params[:post][:name] == #location.name)
#post.location_id = #location.id
#location.rating_count += 1
#location.rating = ( (#location.rating + params[:post][:rating].to_r.to_d )/ #location.rating_count )
else
#post.location_id = Location.create(:address => params[:post][:address], :name => params[:post][:name], :rating => params[:post][:rating].to_r.to_d).id
end
#post.save
respond_to do |format|
if #post.save
#location.save! #Update the location only if the post was successful
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
I know this is a LARGE post, but the main point is, 2 identical validations are failing in one place and succeeding in another. One of them validates attributes which are part of the database, the other validates virtual attributes, and I suspect this is the underlying problem.

validates :rating, :inclusion => ['0', '1', '2', '3', '4', '5']
# OR
validates :rating, :inclusion => (0..5).map(&:to_s)
UPD
add this method
def rating=param
#rating = param.to_f
end
so now you can use your original validation
validates :rating, :inclusion => (0..5)

You need to store an integer value 3 in rating, not the string "3".
Something like:
model.rating = params[:rating].to_i

Related

WHERE condition is invalid when trying to save

I am developing a Rails application which uses a many-to-many relationship, which needs extra attributes on the join table (apart from the ids of the two related entities).
In the model, a Product can belong to many Orders, and an Order can have many Products.
So I created an action for assigning Products to a given Order, called add_product_order, and the controller method that handles the form when it's sent is finish_add_product_order:
def finish_add_product_order
#order = Order.find(params[:id])
#product = Product.find(params[:order][:products])
if #op = OrdersProduct.find_by_order_id_and_product_id(#order.id, #product.id)
#op.quantity = params['quantity'][0]
else
#op = OrdersProduct.new(:order => #order, :product => #product, :quantity => params['quantity'][0])
end
respond_to do |format|
if #op.save
format.html { redirect_to #order, notice: 'Order was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "add_product_order", notice: 'No se ha podido grabar.' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
The orders_products table (which is used for storing the related ids and quantities) has the following index:
add_index :orders_products, [:order_id, :product_id], :unique => true
The action above (finish_add_product_order) works fine if the Product isn't already in the Order (i.e. OrdersProduct.new is being called). But this is the error I get when it already exists and I try to save it:
SQLite3::SQLException: no such column: orders_products.: UPDATE "orders_products" SET "quantity" = 4 WHERE "orders_products"."" IS NULL
The request parameters are the following, just in case:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"TwPMk73MhCM2IeMfmf2g/fdm3+ahpyaxs1InULC/8ig=",
"order"=>{"products"=>"4"},
"quantity"=>["4"],
"commit"=>"Update Order",
"id"=>"2"}
I believe the WHERE clause should include both the order and the product id so as to identify the row affected, but I don't know why the column names are not being included, neither why it ends with "IS NULL".
Any suggestions? Thanks
EDIT
This is the code for the three classes involved:
class Order < ActiveRecord::Base
belongs_to :user
has_many :gardens, :dependent => :destroy
has_many :products, :through => :orders_products
has_many :orders_products
attr_accessible :address, :delivery_date, :paid, :user, :orders_products_attributes
# hid some irrelevant methods
end
class Product < ActiveRecord::Base
has_many :gardens, :through => :gardens_products
has_many :gardens_products
has_many :orders, :through => :orders_products
has_many :orders_products
attr_accessible :cost, :description, :stock, :title, :x_size, :y_size, :pivot_x_position, :pivot_y_position, :is_gallery_product
validates_presence_of :cost, :stock, :title, :x_size, :y_size, :pivot_x_position, :pivot_y_position
validates_numericality_of :cost, :stock, :x_size, :y_size, :pivot_x_position, :pivot_y_position, :only_integer => true, :message => "Solo puede ser un numero entero"
validates_inclusion_of :cost, :in => 0..1000000, :message => "Solo puede estar entre 0 y 1 000 000"
validates_inclusion_of :stock, :in => 0..10000000, :message => "Solo puede estar entre 0 y 10 000 000"
validates_inclusion_of :x_size, :y_size, :in => 1..200, :message => "Solo puede estar entre 0 y 200"
# Poner el rango variable en los pivotes (dentro del rango del tamano)
validates_inclusion_of :pivot_x_position, :in => 1..200, :message => "El pivote debe estar dentro de las dimensiones del producto"
validates_inclusion_of :pivot_y_position, :in => 1..200, :message => "El pivote debe estar dentro de las dimensiones del producto"
validates :title, :length => { :minimum => 5},
:presence => { :message => "Debe tener un titulo de largo mayor que 5 caracteres" }
end
class OrdersProduct < ActiveRecord::Base
belongs_to :order, :dependent => :destroy
belongs_to :product
attr_accessible :order, :product, :quantity
validates_numericality_of :quantity,
:only_integer => true,
:message => "solo puede ser un numero entero"
validates_inclusion_of :quantity,
:in => 1..1000,
:message => "solo puede estar entre 1 y 1 000"
end
Thanks again
Replace this find call "find_by_order_id_and_product_id" with a "where" call and get the first object of the returned Relation object by calling .first.
if #op = OrdersProduct.where(:order_id => #order.id, :product_id => #product.id).first
#op.quantity = params['quantity'][0]
else
#op = OrdersProduct.new(:order => #order, :product => #product, :quantity => params['quantity'][0])
end
Let me know if that helps.
Since the orders_products table had been created without an id (with create_table :orders_products, :id => :false do |t|...), all I had to do was to add an id to it, with this migration:
class AddIdToOrdersProduct < ActiveRecord::Migration
def change
add_column :orders_products, :id, :primary_key
end
end
that way, it worked after doing rake db:migrate.

Rails conditional validation of Zip Code for many countries

I need a model-level validation for Zip codes in USA and Canada. This code makes me feel bad:
zip_regex_usa = %r{\d{5}(-\d{4})?}
zip_regex_canada = %r{[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d}
validates :shipping_zip, :presence => true, :format => { :with => zip_regex_usa }, :if => :shipping_to_usa?
validates :shipping_zip, :presence => true, :format => { :with => zip_regex_canada }, :if => :shipping_to_canada?
validates :billing_zip, :presence => true, :format => { :with => zip_regex_usa }, :if => :billing_to_usa?
validates :billing_zip, :presence => true, :format => { :with => zip_regex_canada }, :if => :billing_to_canada?
def shipping_to_usa?
shipping_country == 'US'
end
def billing_to_usa?
billing_country == 'US'
end
def shipping_to_canada?
shipping_country == 'CA'
end
def billing_to_canada?
billing_country == 'CA'
end
How to make this code more elegant, writing a single validation line for each field?
You can use gem validates_as_postal_code
It allows you to check zip codes like this:
class Person < ActiveRecord::Base
validates_as_postal_code :postal_code, :country => "CA", :allow_blank => true
end
and there're more options
EDIT:
There's also one nice gem: going_postal check it out!
I pulled some bits together into this gem: validates_zipcode.
It currently supports 259 countries zipcode formats and plays nice with Rails 3 & 4.
You can use it like this:
class Address < ActiveRecord::Base
validates_zipcode :zipcode
validates :zipcode, zipcode: true
validates :zipcode, zipcode: { country_code: :ru }
validates :zipcode, zipcode: { country_code_attribute: :my_zipcode }
end

How make a drop down menu reflect what's stored in it's corresponding db column in ruby on rails?

How do I make a drop down menu reflect what's stored in it's corresponding column in a database?
I have a dropdown menu for gender selection and it updates the database fine
but goes back to default option in select menu on refresh where as all my text fields are pulling db data fine.
<%= form_for #profile, :remote => true, do |f| %>
Username: <%= #profile.user.username %><br />
URL: http://site.com/<%= #profile.user.username %><br />
First Name: <%= f.text_field :first_name, %><br />
Last Name: <%= f.text_field :last_name, %><br />
I am: <%= f.select :gender, options_for_select([['Select Gender', ''],['Male','m'],['Female','f']]) %><br />
<%= f.submit 'update' %><br />
<% end %>
Any clue what I'm missing?
Kind regards
Here's my model:
class Profile < ActiveRecord::Base
belongs_to :user
attr_accessible :first_name, :last_name, :gender, :motd
# Local Variables
# Regex Variables
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
alpha_regex = /^[a-zA-Z]*$/
alpha_numeric_regix = /^[a-zA-Z0-9_]*$/
#Form Validation
validates :first_name, :presence => true,
:length => { :minimum => 2, :maximum => 15 },
:format => {
:with => alpha_regex,
:message => "Your first name must contain letters only"
}
validates :last_name, :presence => true,
:length => { :minimum => 2, :maximum => 15 },
:format => {
:with => alpha_regex,
:message => "Your last name must contain letters only"
}
validates :gender, :presence => true,
:inclusion => {
:in => %w( m f ), :message => "Are you male or female?"
}
end
Update method from controller
def update
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.js { render :js => "window.location = '#{settings_edit_profile_path}'" }
flash[:success] = "Profile updated"
else
format.js { render :form_errors }
end
end
end
options_for_select has a special syntax for selecting a value:
<%= f.select :gender, options_for_select([['Select Gender', ''],['Male','m'],['Female','f']], "#{#profile.gender}") %>
might work like you expect.
Or you could create a Gender model and use collection_select which does this by default:
<%= f.collection_select :gender, Gender.all, :value, :description, :prompt => true %>

Problem with Twitter search api

working when using this in rails c
client = TwitterSearch::Client.new('campvote')
tweets = client.query('#barcampmlk2 #railsforzombies +1')
but not when BarcampSession.update_twitter! it retune empty hash
require 'twitter_search'
class BarcampSession < ActiveRecord::Base
validates :hash_tag , :format => {:with => /^#\w+/ } , :presence => true ,:uniqueness => true
validates :name , :presence => true
validates :email , :presence => true , :format => {:with => /((\S+)#(\S{3}[a-zA-z0-9)]\S*))/ }
validates :handphone, :presence => true
def self.update_twitter!
client = TwitterSearch::Client.new('campvote')
BarcampSession.all.each do |sess|
tweets = client.query('#barcampmlk2 #{sess.hash_tag} +1')
puts tweets.to_yaml
end
end
end
it return
rb(main):014:0> BarcampSession.update_twitter!
--- !seq:TwitterSearch::Tweets []
=> [#<BarcampSession id: 1, hash_tag: "#railsforzombies", name: "wizztjh", email: "wiz123tjh#gmail.com", handphone: "1234006", since: nil, created_at: "2010-12-14 18:28:01", updated_at: "2010-12-14 18:28:01">]
String interpolation works only with double quotes, not single quotes. Change the line
tweets = client.query('#barcampmlk2 #{sess.hash_tag} +1')
to
tweets = client.query("#barcampmlk2 #{sess.hash_tag} +1")

Rails VALIDATES

I have the following:
validates :fname, :presence => true, :length => { :minimum => 2 }
How do I add a message to that? Right now the errors says "Fname is too short (minimum is 2 characters)" I'd like it to say First Name and not Fname.
thanks?
could you try this?
validates :fname, :presence => true, :length => { :minimum => 2 },
:format => {
:message => 'your message.'}
Solution ended up being:
Change the name of the field in your locale file:
en:
activerecord:
attributes:
user:
fname: First name
validates :fname, :presence => true, :length => { :minimum => 2 }, :message => "your message goes here"
UPDATE
If you want to put a friendly column name use code like this:
class User < ActiveRecord::Base
HUMANIZED_ATTRIBUTES = {
:email => "E-mail address"
}
def self.human_attribute_name(attr)
HUMANIZED_ATTRIBUTES[attr.to_sym] || super
end
end
Other way to solve this problem is to take the approach described in this blog post: http://www.softiesonrails.com/2008/4/23/better-messages-for-activerecord-validation-errors

Resources