What's the Cleanest Way to Override Activerecord's Find for Both Models and Collections

Is there a way to get a collection of all the Models in your Rails app?

EDIT: Look at the comments and other answers. There are smarter answers than this one! Or try to improve this one as community wiki.

Models do not register themselves to a master object, so no, Rails does not have the list of models.

But you could still look in the content of the models directory of your application...

Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
# ...
end

EDIT: Another (wild) idea would be to use Ruby reflection to search for every classes that extends ActiveRecord::Base. Don't know how you can list all the classes though...

EDIT: Just for fun, I found a way to list all classes

Module.constants.select { |c| (eval c).is_a? Class }

EDIT: Finally succeeded in listing all models without looking at directories

Module.constants.select do |constant_name|
constant = eval constant_name
if not constant.nil? and constant.is_a? Class and constant.superclass == ActiveRecord::Base
constant
end
end

If you want to handle derived class too, then you will need to test the whole superclass chain. I did it by adding a method to the Class class:

class Class
def extend?(klass)
not superclass.nil? and ( superclass == klass or superclass.extend? klass )
end
end

def models
Module.constants.select do |constant_name|
constant = eval constant_name
if not constant.nil? and constant.is_a? Class and constant.extend? ActiveRecord::Base
constant
end
end
end

Rails: Overriding ActiveRecord association method

You can use block with has_many to extend your association with methods. See comment "Use a block to extend your associations" here.

Overriding existing methods also works, don't know whether it is a good idea however.

  has_many :tags, :through => :taggings, :order => :name do
def << (value)
"overriden" #your code here
super value
end
end

Clean way to return ActiveRecord object with associations

Solution

A DRY approach is inside your models, you can define a attributes method and have it return the shape of the object that you want the render function to use.

# thing.rb

def attributes
# Return the shape of the object
# You can use symbols if you like instead of string keys
{
'id' => id, # id of the current thing
'other_fields' => other_fields, # add other fields you want in the serialized result
'object_a' => object_a, # explicitly add the associations
'object_b' => object_b
}
end

The associations object_a and object_b should get serialized as normal. You can repeat the same approach for them by adding an attributes method in their respective classes if you want to limit/customize their serialized result.

So when render json: is called on a single, or a collection of, thing model(s), the shape of the json objects returned will be as defined in the method above.

Note:

One caveat is your key names in the hash being returned in attributes has to match the name of the method (or association name). I'm not too sure why. But the workaround that I've used when needing to add a key with a different name than its corresponding column is to make a method in the model of the key name I want to use.

For example, say your Thing model has a column name, but in your json result you want the key name that corresponds to that column to be called name_of_thing. You'd do the following:

def name_of_thing
name
end

def attributes
{
'name_of_thing' => name_of_thing,
# other fields follow
# ...
}
end


Conditional Logic

Conditions dependent on fields/associations in the model

The attributes method can support conditional based on fields in the model.

# thing.rb

def attributes
result = {}

result['id'] = id
# add other fields

# For example, if association to object_a exists
if object_a
result.merge!({
'object_a' => object_a
})
end

# return result
result
end

Conditions dependent on input from outside the model

If you want to make your method render different fields in different places, one thing you can do is override the as_json method, which can work better for these cases because this method accepts options in a parameter.

# thing.rb

def as_json(options = {})
result = {}

result['id'] = id
# add other fields to result

if(options[:show_only_ids])
result.merge!({
'object_a_id' => object_a_id,
'object_b_id' => object_b_id
})
else
result.merge!({
'object_a' => object_a,
'object_b' => object_b
})
end

# return result
result
end

Then you need to modify your controller (or wherever you're calling the serialization of the Thing model) to pass in the appropriate option when necessary.

# thing_controller.rb

render json: Thing.all.as_json({ show_only_ids: true })

When rendering, you don't always have to explicitly specify the as_json. The render function will call it anyway by default. You only need to explicitly make that call when you want to pass in options.

Declarative_Athorization: with_permissions_to for all models on single place

Try adding:

default_scope { with_permissions_to(:read) }

to your models

What is the right way to override a setter method in Ruby on Rails?

===========================================================================
Update: July 19, 2017

Now the Rails documentation is also suggesting to use super like this:

class Model < ActiveRecord::Base

def attribute_name=(value)
# custom actions
###
super(value)
end

end

===========================================================================

Original Answer

If you want to override the setter methods for columns of a table while accessing through models, this is the way to do it.

class Model < ActiveRecord::Base
attr_accessible :attribute_name

def attribute_name=(value)
# custom actions
###
write_attribute(:attribute_name, value)
# this is same as self[:attribute_name] = value
end

end

See Overriding default accessors in the Rails documentation.

So, your first method is the correct way to override column setters in Models of Ruby on Rails. These accessors are already provided by Rails to access the columns of the table as attributes of the model. This is what we call ActiveRecord ORM mapping.

Also keep in mind that the attr_accessible at the top of the model has nothing to do with accessors. It has a completely different functionlity (see this question)

But in pure Ruby, if you have defined accessors for a class and want to override the setter, you have to make use of instance variable like this:

class Person
attr_accessor :name
end

class NewPerson < Person
def name=(value)
# do something
@name = value
end
end

This will be easier to understand once you know what attr_accessor does. The code attr_accessor :name is equivalent to these two methods (getter and setter)

def name # getter
@name
end

def name=(value) # setter
@name = value
end

Also your second method fails because it will cause an infinite loop as you are calling the same method attribute_name= inside that method.

Does adding a :before_save to an AR model override one already added through a custom extension to AR?

To clarify, both the default added above and any customized before_save callback added to a specific model will execute. . . I believe same rules as described under "Inheritable callback queues" in the ActiveRecord::Callbacks docs will apply. Callback macros like before_save "add behavior into a callback queue that is kept intact down through an inheritance hierarchy." – Jordan



Related Topics



Leave a reply



Submit