Why does Model.new in Rails 3 do an implicit conversion? - ruby-on-rails

I'm facing a weird behavior in Rails 3 model instantiation.
So, I have a simple model :
class MyModel < ActiveRecord::Base
validates_format_of :val, :with => /^\d+$/, :message => 'Must be an integer value.'
end
Then a simple controller :
def create
#mod = MyModel.new(params[:my_model])
if #mod.save
...
end
end
first, params[:my_model].inspect returns :
{:val => 'coucou :)'}
But after calling #mod = MyModel.new(params[:my_model]) ...
Now, if I call #mod.val.inspect I will get :
0
Why am I not getting the original string ?
At the end the validates succeed because val is indeed an integer.
Is this because val is defined as an integer in the database ?
How do I avoid this behavior and let the validation do his job ?

If val is defined as an integer in your schema then calling #my_model.val will always return an integer because AR does typecasting. That's not new to rails 3, it's always worked that way. If you want the original string value assigned in the controller, try #my_model.val_before_type_cast. Note that validates_format_of performs its validation on this pre-typecast value, so you don't need to specify that there.
EDIT
Sorry I was wrong about the "performs its validation on this pre-typecast value" part. Looking at the code of the validation, it calls .to_s on the post-typecast value which in your case returns "0" and therefore passes validation.
I'd suggest not bothering with this validation to be honest. If 0 is not a valid value for this column then validate that directly, otherwise just rely on the typecasting. If the user enters 123 foo you'll end up with 123 in the database which is usually just fine.

There is also better fitting validator for your case:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#validates_numericality_of

Related

Ruby on Rails 4.2 enum attributes

I'm trying to use new Enum type, everything works well except one issue. When writing functional tests I usually use structure:
order = Order.new(o_status: :one)
post :create, order: order.attributes
# Error message:
# ArgumentError: '0' is not a valid o_status
It's ok as long as I don't have Enum attribute. The problem with enums is that instead of String value .attributes returns it's Integer value which can't be posted as enum attribute value.
In above example model can look like this:
class Order < ActiveRecord::Base
enum o_status: [:one, :two]
end
I figured out that when I do:
order = Order.new(o_status: :one)
atts = order.attributes
atts[:o_status] = "one" # it must be string "one" not symbol or integer 0
post :create, order: order.attributes
It will work OK.
Is it normal or there is some better solution?
EDIT:
The only workaround which I found looks like this:
order = { o_status: :one.to_s }
post :create, order: order
pros: It is short and neat
cons: I cannot validate order with order.valid? before sending with post
This doesn't solve issue with order.attributes when there is Enum inside.
From the Enum documentation:
You can set the default value from the database declaration, like:
create_table :conversations do |t|
t.column :status, :integer, default: 0
end
Good practice is to let the first declared status be the default.
Best to follow that advice and avoid setting a value for an enum as part of create. Having a default value for a column does work in tests as well.

How do I use the value of an attribute within a model? Ruby on Rails

Basically, I have a model, Degree, and it has three attributes: degree_type, awarded_by, and date_awarded.
There are two arrays of values that should be valid for awarded_by. The two valid values for degree_type are "one" and "two", and the valid values for awarded_by depend on "one" and "two".
If degree_type is "one" (has a value of "one", that a user would put in), I want the valid values for awarded_by to be array_one. If degree_type has a value of "two", I want the valid values for awarded_by to be array_two.
Here is the code so far:
class Degree < ActiveRecord::Base
extend School
validates :degree_type, presence: true,
inclusion: { in: ["one",
"two"],
message: "is not a valid degree type"
}
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(awarded_by_type) }
end
Degree.schools outputs an array depending on the degree type, so Degree.schools("one") would return array_one, where
array_one = ['school01', 'school02'...]
My problem is, I don't know how to access the value of degree_type within the model.
What I tried below doesn't work:
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(:degree_type) }
I tried using before_type_cast but I was either using it incorrectly or there was another problem, as I couldn't get that to work either.
When I test this I get:
An object with the method #include? or a proc, lambda or symbol is required, and must be supplied as the :in (or :within) option of the configuration hash
Help me out? :) If any more info is needed, let me know.
EDIT: To add to this, I double checked it wasn't my Degree.schools method acting up - if I go into the rails console and try Degree.schools("one") or Degree.schools("two") I do get the array I should get. :)
EDIT again: When I tried #Jordan's answer, I got errors in the cases where the awarded_by was incorrect because in those cases, valid_awarded_by_values was nil and there is no include? method for a nil object. Therefore I added an if statement checking for whether valid_awarded_by_values was nil or not (so as to return if it was), and that solved the problem!
I put this inside the method, before the unless statement and after the valid_awarded_by_values declaration:
if valid_awarded_by_values.nil?
error_msg = "is not a valid awarded_by"
errors.add(:awarded_by, error_msg)
return
end
The easiest way will be to write a custom validation method, as described in the Active Record Validations Rails Guide.
In your case, it might look something like this:
class Degree < ActiveRecord::Base
validate :validate_awarded_by_inclusion_dependent_on_degree_type
# ...
def validate_awarded_by_inclusion_dependent_on_degree_type
valid_awarded_by_values = Degree.schools(degree_type)
unless valid_awarded_by_values.include?(awarded_by)
error_msg = "must be " << valid_awarded_by_values.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
errors.add(:awarded_by, error_msg)
end
end
end

Validate Param Types in Rails

I've been looking all over the place and I'm wondering if I'm doing something wrong. And just to double check, I'll ask you guys!
So I'm receiving params in a Rails controller. One key, value pair is :status => true/false. However, I find that when I try to post status as a string like
:status => "THIS IS NOT A BOOLEAN"
and create my object in my controller, the :status attribute of my object becomes false.
Therefore, is there any clean way in rails to validate that my :status corresponds to a boolean?
Thanks!
This very strange method will to the trick
def is_boolean?(item)
!!item == item
end
params[:status] = 'some string'
is_boolean?(params[:status])
# => false
params[:status] = true
is_boolean?(params[:status])
# => true
A slightly more intuitive version would be
def is_boolean?(item)
item == false || item == true
end
Validation
The Rails way to do it is to validate in the model (from the docs):
#app/models/model.rb
Class Model < ActiveRecord::Base
validates :status, inclusion: { in: [true, false] }, message: "True / False Required!"
end
--
MVC
The reason for this is twofold:
DRY
MVC
If you want to keep your application DRY, you need to make sure you have only one reference to a validation throughout. Known as the "Single Source Of Truth", it means if you try and populate the model with other controllers / methods, you'll still invoke the same validation
Secondly, you need to consider the MVC (Model-View-Controller) pattern. MVC is a core aspect of Rails, and means you have to use your controller to collate data only - pulling & compiling data in the model. This is also true for validations -- always make sure you keep your validations with the data (IE in the model)
The above #Iceman solution is good if you are only doing it once place but you keep doing/repeating it in other places i suggest you to create to_bool method. i.e
class String
def to_bool
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
end
end
and put this method in intializer or in library. And, you can simply do this
Mymodel.new(status: params[:status].to_s.to_bool)
we are doing to_s just because to convert nil to '' incase the status key isn't in params .

Updating Attribute through reference

I am wondering if it's possible to reference an object's attribute.
The object User have attribute first_name, so normally if we want to update the first name attribute we do:
user0 = User.new()
user0.update_attribute(:first_name, "joe")
now my question is can I update the :first_name attribute through another variable/symbol like so:
user0.update_attribute(:fname_ref, "jack")
user0.first_name #=> has the value jack
I was looking for variable reference like in Perl, but came up nothing in Ruby.
---------- EDIT
I am in the middle of doing the lynda ruby on rails tutorial, and in the middle of creating a module to adjust positions of items in a table.
unfortunately when I first started I named my tables columns differently
pages.page_position, subjects.subject_position, sections.section_position
now the module PositionMover is to be used accross three models, so now
I have a problem since the attributes names are different for each model
so I thought no worry I'll just create a pointer / refference for each model
:position = :pages_position , :position = :subjects_position , :position = :section_position
hence the question , if its even possible to do it.
if its not possible , any suggestion what should I do , so the module can
be used accross three different models , with different attribute names.
sorry I am a newbie.
Symbols are not like variables, they are actually a object type, like String or Fixnum so you can have a variable that is of type symbol.
I think this is what you are looking for:
attribute = :first_name
user0.update_attribute(attribute, "jack")
user0.first_name #=> has the value jack
Update: If you have a String and need to convert to a symbol, (I'm not sure if you need this for update_attribute)
foo = "string"
foo.to_sym #=> :string
Use the alias_attribute . Define into each model like :
Page model
alias_attribute :position , :pages_position
Subject Model
alias_attribute :position , :subjects_position
Section Model
alias_attribute :position , :section_position
And use (Model.position = values) with each model . Hope Its solution of your problem .
You can also use send docs and use symbols or strings to reference the attribute's methods. send can be incredibly useful since it enables you to choose the method that you'll be invoking at runtime.
Eg
user.first_name = "Jack" # set first_name to Jack
# Note: method "first_name=" (a settor) is being used
attribute = "first_name"
user.send attribute + "=", "Jack" # set first_name to Jack
# The string "first_name=" is coerced into
# a symbol
attribute = :first_name
val = user.send attribute # => returns "Jack"
see the definition of update_attribute in the ActiveRecord::Persistence module on github:
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
send("#{name}=", value)
save(:validate => false)
end
this leads me to believe you could add the following to your model to achieve that behavior:
alias_method :fname_ref= :first_name=
I'd be interested to know why you want to do that as #Andrew Marshall asked.

Rails 3 - custom validation to check date type

I want to validate my date (which actually have DATE type) in model. So, i try to write for that simle method and run it via validation.
Teacher
class Teacher < ActiveRecord::Base
attr_accessible :teacher_birthday # DATE type!
belongs_to :user
validates :teacher_birthday, :presence => true,
:unless => :date_is_correct?
########
def date_is_correct?
parsed_data = Date._parse(:teacher_birthday)
input_day = parsed_data[:mday]
input_month = parsed_data[:mon]
input_year = parsed_data[:year]
correct_days = 1..31
correct_months = 1..12
correct_year = 1900..2000
if ( correct_days.member? input_day ) and ( correct_months.member? input_month) and
( correct_year.member? input_year)
true
else
errors.add(:teacher_birthday, 'date is invalid')
false
end
end
When i run rspec a lot of tests fail.
TypeError: can't convert Symbol into String
# ./app/models/teacher.rb:56:in `_parse'
# ./app/models/teacher.rb:56:in `date_is_correct?'
I suppose i do something wrong. Can someone tell me what is wrong?
This isn't necessary at all. If Date.parse(:teacher_birthday) returns a date and doesn't raise an exception, you have a valid date.
Date._parse expects a string-value containing a date and will in your code always try to parse 'teacher_birthday'. You need to get the value of the field first and pass the value to Date._parse. ActiveRecord creates methods with the same name as the field to get the value.
Any of the following will work:
Short way
parsed_data = Date._parse(teacher_birthday)
Identically to the first (the self. is added for you during parsing)
parsed_data = Date._parse(self.teacher_birthday)
Explicit way
parsed_data = Date._parse(self[:teacher_birthday])
A new gem has been created to help validate types in rails and an explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code would simple be:
class Post < ActiveRecord::Base
validates_type :teacher_birthday, :date
end
This will throw an exception when anything except a a Date is assigned to :teacher_birthday.

Resources