How to Overwrite a Getter Method in an Activerecord Model

How can I overwrite a getter method in an ActiveRecord model?

The Rails Style Guide recommends using self[:attr] over read_attribute(:attr).

You can use it like this:

def name
name_trans || self[:name]
end

rails override default getter for a relationship (belongs_to)

alias_method is your friend here.

alias_method :original_bar, :bar
def bar
self.original_bar || Bar.last
end

The way this works is that you alias the default "bar" method as "original bar" and then implement your own version of "bar". If the call to original_bar returns nil then you return the last Bar instance instead.

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.

ActiveRecord: How to grab record column value if I overwrite the getter method?

Use read_attribute:

def email
guest? ? guest_email : read_attribute(:email)
end

Alternatively, you can use self[:attribute] as pointed out by @Stefan:

def email
guest? ? guest_email : self[:email]
end

Overwrite has_many getter

Technically you can add an alias to make sure the rest of the code doesn't break. The idea is to rename your existing has_many method to something else and use the existing key_phrases as a normal method.

This way you don't have to change anywhere else in the codebase and this will work with minimum changes.

Read more here about alias_attribute

class Metric < ApplicationRecord

alias_attribute :phrases, :key_phrases #NOTE the alias_attribute should be before `key_phrases`
has_many key_phrases

def key_phrases
# your logic goes into this method
if phrases.empty?
# create
else
phrases
end
end
end

However... I personally create a more meaning full method and leave the "has_many key_phrases" as it is, the reason is, this implies and gets and if you are trying to create records in the same method it's a bit confusing.

So, I would do something like this

class Metric < ApplicationRecord

has_many key_phrases

def get_or_create_key_phrases(*params)
if phrases.empty?
# create
else
phrases
end
end
end

and then change all the places that call key_phrases to get_or_create_key_phrases, which I personally think more explicit. But as per the downside, you'll have to change more places in the code.

Overriding a has_many association getter

Use super:

class User < ActiveRecord::Base
has_many :cars

def cars
# Pretend this is complex logic
super.order(:num_wheels)
end
end

when you use a macro like has_many, Rails dynamically creates a module(which could be accessed by User.generated_association_methods).In your case, define the accessors and readers(such as "cars" in your case, which could be accessed by User.generated_association_methods.instance_methods). This module becomes the ancestor of your User class, so you can access the reader method(cars) by "super" in your own cars method.

How do I override an attr_accessor getter from a module?

Are you sure you want the attr_accessor? Wouldn't an attr_writer suffice?

require 'active_support/all'

module HasUrl
extend ActiveSupport::Concern

included do
attr_writer :bar
end

def bar
0
end
end

class Foo
include HasUrl
end

p Foo.new.bar

Anyhow, if you really want to use attr_accessor, this should work:

require 'active_support/all'

module HasUrl
extend ActiveSupport::Concern

included do
attr_accessor :bar
define_method :bar do
0
end
end
end

class Foo
include HasUrl
end

p Foo.new.bar

Hash getter and setter on ActiveRecord object

First to say: I dont think thats the best solution. When you touch this code again in lets say 3 years it will be like "WHAAAAAT HAVE I DONE?"... It whould the best solution to replace all the snippets in your code with other code.

You can prepend the method_missing method of the object after removing this line serialize :state to fetch all calls that want to access the not anymore existing serialized field of the object. Its explained here:

http://blog.enriquez.me/2010/2/21/dont-forget-about-respond-to-when-implementing-method-missing/

Its called metaprogramming. Thats the "rails magic" that makes all the find_by_attribute_name stuff working without defining each of these methods. Can be cool stuff but you need to be very carefull and you need to know what your doing.

Overrriding ActiveRecord attribute getter doesn't produce consistent results

You might want to try using an enum in your model, like below:

enum priority: { high: 1, normal: 2, low: 3}

see the documentation here http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html



Related Topics



Leave a reply



Submit