Rails STI subclasses validation on update_attributes - ruby-on-rails

I would like to know if there's a way to when doing STI the update_attributes, validate the attributes based on the new class type?
For e.g. suppose i have:
class A < ActiveRecord::Base
end
class B < A
validates :attribute_z, :presence => true
end
class C < A
validates :attribute_x, :presence => true
validates :attribute_y, :presence => true
end
If i run (the way rails is implemented):
b = A.find('b-id')
b.update_attributes({ 'type' => 'C', :attribute_x => 'present', :attribute_y => 'present', :attribute_z => nil }) # will return false with errors on 'attribute_z must be present'
I've tried with #becomes:
b = A.find('b-id')
b = b.becomes(C)
b.update_attributes({ 'type' => 'C', :attribute_x => 'present', :attribute_y => 'present', :attribute_z => nil })
# this works partially, because the validations are ok but when i look to console i get something like:
UPDATE "as" SET "type" = 'c', "attribute_z" = NULL, "attribute_y' = 'present', 'attribute_x' = 'present' WHERE "as"."type" IN ('C') AND "as"."id" = 'b-id'
# which is terrible because it's looking for a record of B type on the C types.

Allied to this topic Callback for changed ActiveRecord attributes?, you can catch any assignments done to type attribute and make "self" to be of different class (A, B or C) by using the becomes method. So whenever you use find method, it'll populate a fresh new model instance with the data that comes from the DB (identified by 'b-id') and it'll automatically forced-cast the model instance to another type if necessary.
Does that help you?

i've made a solution: https://gist.github.com/4532583, since the condition is added internally by rails (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/inheritance.rb#L15) i've created a new "update_attributes" method that changes the type if the type is given. :)

Related

mongoid equivalent of loaded?

In a Rails app, using ActiveRecord with mysql, you can check to see if an association has been loaded:
class A
belongs_to :b
a = A.find(...
a.b.loaded? # returns whether the associated object has been loaded
Is there an equivalent in mongoid? ._loaded? used to work but no longer does.
UPDATE - adding example
class A
include Mongoid::Document
end
class B
include Mongoid::Document
belongs_to :a
end
a = A.new
b = B.new
b.a = a
b.a._loaded?
returns:
ArgumentError (wrong number of arguments (given 0, expected 1))
It's a enumerable method of this Class: Mongoid::Relations::Targets::Enumerable
_loaded?
it will return true and false if Has the enumerable been _loaded? This will be true if the criteria has been executed or we manually load the entire thing.
Maybe the purpose was not the same.
Now (Mongoid 7.0) _loaded?() is a private method, and in my case it always returns true.
The best I could find is ivar(): it returns the object if already loaded, or false if not loaded.
I'm not sure if this is a reliable solution. It depends on further availability of ivar() and the way objects are stored as instance variables.
> b = B.find('xxx')
=> (db request for "b")
> b.ivar('a')
=> false
> b.a
=> (db request for "a")
> b.ivar('a')
=> (returns "a" object, as when b.a is called)
You can test if includes(:your_association) has been added to the criteria like this:
inclusions = Criteria.inclusions.map(&:class_name)
inclusions.include?('YourAssociation)
For example:
Children.all.include(:parent).inclusions
=> [<Mongoid::Association::Referenced::BelongsTo:0x00007fce08c76040
#class_name="Parent", ...]
Children.all.inclusions
=> []

When does validate method get called in Rails?

I have a model StudentProductRelationship. I am adding a custom validator
validate :validate_primary_product , :if => "!primary_product"
The method is
def validate_primary_tag
unless StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id)
errors.add(:base,"There is no primary product associated to product")
else
end
end
primary_product is a boolean field. I want to validate presence of at least one true primary_product for student_id. The problem is if I have an StudentProductRelationship object say spr with primary_product = true. If I do spr.update_attributes(primary_product: false). The validation does not raise an error because StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id) exists beacuse spr still exists in db with primary_product = true. How do i surpass this?
Doesn't validates_presence_of :primary_product, scope: :student_id work for your?

Rails: .create nullifies a custom value for :id

When I execute a Model.create method, if I specify a value for :id, it later gets nullified. Example:
Model.create (
:id => 50,
:name => Joe,
:enabled => yes
)
Instead what I have to do is use a .new and store it in a class variable, store my id value via the class variable, and then finally call a save:
m = Model.new (
:name => Joe,
:enabled => yes
)
m.id = 50
m.save
I am trying to execute this code in a seeds.rb, and this is NOT very DRY code. How can I do this better and achieve the same results?
id is just attr_protected. To prevent that, you can override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Model < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
or go with #Leo answer
This might be an answer for you. Model.create is basically a Model.new followed by a Model.save and since you are changing the id and saving again you might as well do
m = Model.new {
:name => Joe,
:enabled => yes
}
m.id = 50
m.save!
That will rid you of doing two saves.

Initializing a variable RUBY

I have a class Sample
Sample.class returns
(id :integer, name :String, date :date)
and A hash has all the given attributes as its keys.
Then how can I initialize a variable of Sample without assigning each attribute independently.
Something like
Sample x = Sample.new
x.(attr) = Hash[attr]
How can I iterate through the attributes, the problem is Hash contains keys which are not part of the class attributes too
class Sample
attr_accessor :id, :name, :date
end
h = {:id => 1, :name => 'foo', :date => 'today', :extra1 => '', :extra2 => ''}
init_hash = h.select{|k,v| Sample.method_defined? "#{k}=" }
# This will work
s = Sample.new
init_hash.each{|k,v| s.send("#{k}=", v)}
# This may work if constructor takes a hash of attributes
s = Sample.new(init_hash)
Take a look at this article on Object initialization. You want an initialize method.
EDIT You might also take a look at this SO post on setting instance variables, which I think is exactly what you're trying to do.
Try this:
class A
attr_accessor :x, :y, :z
end
a = A.new
my_hash = {:x => 1, :y => 2, :z => 3, :nono => 5}
If you do not have the list of attributes that can be assigned from the hash, you can do this:
my_attributes = (a.methods & my_hash.keys)
Use a.instance_variable_set(:#x = 1) syntax to assign values:
my_attributes.each do |attr|
a.instance_variable_set("##{attr.to_s}".to_sym, my_hash[attr])
end
Note(Thanks to Abe): This assumes that either all attributes to be updated have getters and setters, or that any attribute which has getter only, does not have a key in my_hash.
Good luck!

Rails3: Defining enum and using it as a custom type for db column

New to rails so not sure what the best approach is here. I want to define a simple c++ style enum which can then be used as a custom type in my db. The enum can be simulated with an array or a custom module but how do I go about turning that into a custom type for my table?
Here's a pattern I follow in rails:
In my model class, I add a module to hold the possible values of the column. Then I put them into an array and define validation against the array of possible values.
Imagine I have a column/attribute called status and it can be three possible values. I'd do this:
class MyModel < ActiveRecord::Base
# This validates that status can't be null
validates :status, :presence => true
# Define a module with all possible values
module Status
IN_DEVELOPMENT = 'in development'
DISABLED = 'disabled'
ACTIVE = 'active'
end
# Now create an array of possible status values, and add a validation
STATUSES = [ Status::DISABLED, Status::ACTIVE, Status::IN_DEVELOPMENT]
validates :status, :inclusion => { :in => STATUSES, :message => "%{value} is not a valid status value" }
end
Have you considered using the built-in enumeration support in your database? Lots of common RDMBSes have enum support, such as Postgres (see http://www.postgresql.org/docs/9.1/static/datatype-enum.html) and MySQL (see http://dev.mysql.com/doc/refman/5.5/en/enum.html). With that, you can directly create the type in your data store and then use it via one of the ActiveRecord plugins (such as enum_type for Postgres: https://github.com/riscfuture/enum_type).
Alternatively, you could use something like active_enum to structure the enumeration as you described and store fields as integers in the database.
Depending on how you plan to utilize this enum type in your code I've found that using scopes accomplishes close to the same thing along with an enum type in the database to ensure only specific values are set.
Example:
scope :trial, :conditions => { :utype => 'TRIAL' }
scope :registered, :conditions => { :utype => 'REGISTERED' }
scope :active, :conditions => { :status => 'ACTIVE' }
scope :abuse, :conditions => { :status => 'ABUSE' }

Resources