Rails: default attribute value conflicting with `validates_inclusion_of` - ruby-on-rails

I have something like this:
class AddTestToPeople < ActiveRecord::Migration
def change
add_column :people, :status, :string, default: "normal"
end
end
class Person < ActiveRecord::Base
validates_inclusion_of :status, in: [ "normal", "super" ]
end
...and the default status value of "normal" doesn't pass the validation when a new record is created. I guess I could just drop the default value, but I'm curious why this doesn't work. Can someone enlighten me?

Default value is set in database.
When you try to insert a record in people table with status attribute set as nil, only then the default value normal would be inserted in the database against status column.
If you are not passing any value to status attribute while saving a new record, its value would be nil. Hence, the validation won't pass. Status would only be set to "normal" at the time of inserting the record.
I would suggest you to modify the model as below, database would take care of the default value:
class Person < ActiveRecord::Base
validates_inclusion_of :status, in: [ "super" ], allow_nil: true
end
Or
Second option would be, as Danny suggested, set up an after_initialize callback and set the default value of status when its not specified. If you take up this option then I don't think that you need a default value at DB level as it status field would always be set from Model.
class Person < ActiveRecord::Base
after_initialize :init_status, if: :new_record?
validates_inclusion_of :status, in: [ "normal","super" ]
def init_status
self.status ||= "normal"
end
end

Related

Rails: handling nullable attribute in the model

I have a model that has a polymorphic association.
class User
belongs_to :address, polymorphic: true, optional: true
end
I call user.address.street on many place of the code, when address is nil I have to use a default address, so I need to check if nil in many parts.
I'd like to find an alternative to prevent this check if nil
you can create a migration to set street as default value as below:
class ChangeStreetDefaultValue < ActiveRecord::Migration
def change
change_column(:table_name, :street, :text, default: DEFAULT_STREET)
ModelName.where(street: nil).update_all(street: DEFAULT_STREET)
end
end
or
you can run the migration to update the default street and run the script on the console separately to update existing records.

When we have a new range for one field in ActiveRecord, can we change the validates code immediately?

Say, if we have
class SomeData < ActiveRecord::Base
validates :foo, inclusion: 33..99
and now, we say, we will make the range narrower, to 66..99, but there are many values in the Database that are still between 33 and 65, can we change the above code to
class SomeData < ActiveRecord::Base
validates :foo, inclusion: 66..99
immediately? Is it a problem if the data is just read into the system? (even in earlier version of Rails such as 3.2?)
Changing a validation does not actually directly effect the existing data in your database in any way. Validations are only run when #valid? is called on the model.
This happens implicitly when you call:
.create and .create!
#save and #save!
#update and #update!
And it causes these methods to bail so that either a rollback occurs or no db query is fired in the first place.
Can we change the validation immediately?
Yes. The validation will only really kick in if you try to update an existing record. In which case a previously valid record may now be invalid. This will not permit an update unless the value is updated to be in the new permitted range.
Dealing with legacy data
If you want to let users still update existing records with what is invalid data according to the new rules there are serveral tricks.
The if: and unless: options
class SomeData < ActiveRecord::Base
validates :foo, inclusion: 33..99, if: :legacy_record?
validates :foo, inclusion: 66..99, unless: :legacy_record?
end
There are several ways to implement :legacy_record? like a boolean flag in the database. Note that these should not be confused with the ruby keywords. They are just hash options.
custom validation methods:
class SomeData < ActiveRecord::Base
validate :my_validation_method
def my_validation_method
rng = legacy_record? ? 33..99 : 66..99
errors.add(:foo, "out of range") unless rng.cover?(foo)
end
end
Single Table Inheritance
class Thing < ActiveRecord::Base
validates :foo, inclusion: 66..99
end
class LegacyThing < ActiveRecord::Base
self.table_name = "things"
validates :foo, inclusion: 33..99
end
In this example you would add a things.type varchar column and update the existing rows with things.type = "LegacyThing". This isn't really STI - it just uses the mechanism built into ActiveRecord.

ruby on rails: before_validation

in domainpost.rb i have this:
class Domainpost < ActiveRecord::Base
attr_accessible :content, :additiona, :registerdate, :expiredate, :registerin, :price
belongs_to :user
before_save { |domainpost| domainpost.content = content.downcase }
before_validation :pricecheck
validates :price, allow_blank: false, presence: true
default_scope order: 'domainposts.created_at DESC'
def pricecheck
if price.blank?
price = 'no price'
end
end
and it isnt work
when price in post is blank after save is stil blank,
any idea what i do wrong?
It doesn't work because instead of setting attribute price of Domainpost instance, you set a local variable. Instead you should do:
def pricecheck
self.price = 'no price' if price.blank?
end
As answer by #Merek Lipka and #muttonlamb you can try those approach but I suggest is defined a default value on your database side
like on your migration for price field simply do this
t.[price_data_type],:price,:default => "Your Default value"
Well this will take care of your check in model I believe

Rails attr_readonly doesn't work

According to this question and the documentation of attr_readonly the following should be possible:
class MyModel < ActiveRecord::Base
attr_accessible :foo
attr_readonly :bar
end
m = MyModel.create(foo: '123', bar: 'bar') # Should work
m.update_attributes(bar: 'baz') # Should not work
However the first one fails, saying that I can't mass-assign bar. What am I mising?
From documentation
attr_accessible takes a list of attributes that will be accessible.
All other attributes will be protected.
So attr_accessible made bar attribute as protected from mass-assignment.
You can make the attribute , suppose ,key as:-
attr_accessible :key
and then add one more validation
validate :check_if_key_changed, :on=> :update
private
def check_if_key_changed
if self.key_changed?
errors.add(:key,"cant change key")
end
end
In this way you will be able to mass-assign it once on creation and can also make sure that it do not get updated.

Rails: Relation with class_name does not get saved properly?

I've got a (rather simple) model representing a comment:
class Comment < ActiveRecord::Base
STATES = [:processing, :accepted, :declined]
belongs_to :note
belongs_to :author, :class_name => 'User'
validates_inclusion_of :state, :in => STATES
validates_presence_of :author
default_scope :order => 'created_at DESC'
def initialize( attributes={} )
super(attributes)
self.state ||= 'processing'
end
end
However, everytime I save a comment (with its fields set properly), the author relation always fails to save (well, actually the comment saves successfully, it just leaves out the author...). This goes as far as Comment.first.valid? returning false due to the validation on the author field (Comment.first.author is nil).
My suspicion is that I handle the default value for state-field in a wrong way? If so, how should I set the default value instead?
thx for your help in advance
About the state attribute, it would be better to use an after_initialize callback to set the default instead of overriding the initialize function :
def after_initialize
self.state ||= 'processing'
end
To properly override a function you should pass params and args this way :
def initialize(*args,&block)
super(*args,&block)
#what-you-want-to-execute
end
Notice that there is often a better way than using this !

Resources