I want to make it so that all of our activerecord models treat a database-calculated column/attribute as read-only.
I could use attr_readonly
, but the issue with that is that it is only enforced on updates. You can still write the
attribute when you create a row.
However, let's say I wanted to go that route. I would do something like this:
module DbTriggers
module Railties
module ActiveRecord
def self.included(base)
base.class_eval do
attr_readonly :__auto_created_at
end
end
end
end
end
::ActiveRecord::Base.send :include, ::DbTriggers::Railties::ActiveRecord
Boom, done. (You can also use extend
and self.extended
).
Ok, let's say we want to handle the case of creates. Well, one of the ways we can do this is overwrite the writer/setter
method for that column __auto_created_at=
.
Easy enough, right?
module DbTriggers
module Railties
module ActiveRecord
def __auto_created_at=(val)
raise ::ActiveRecord::ActiveRecordError, '__auto_modified_at is read-only'
end
end
end
end
::ActiveRecord::Base.send :include, ::DbTriggers::Railties::ActiveRecord
class MyModel < ::ActiveRecord::Base
end
# ActiveRecord::DangerousAttributeError:
# __auto_created_at= is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
Wha wha whaaaaa?? Well, thank you, ActiveRecord... not really.
So, what happens here is this defines ActiveRecord::Base#__auto_created_at=
. Then, when you get around to defining
MyModel
, Rails goes and inspects the database and creates attribute readers/writers for all the columns on the fly. As
it's doing that, it tries to recreate __auto_created_at=
. Turns out that ActiveRecord has a hook after method
definition on its subclasses to check if you're overwriting a method that's already been defined by ActiveRecord. It
does not like that.
So what are we to do? Maybe there are other options here, but one solution is to automatically define
__auto_created_at=
on our own subclasses.
module DbTriggers
module Railties
module ActiveRecord
def self.included(base)
class << base
prepend ClassMethods
end
end
module ClassMethods
def inherited(base)
super
base.class_eval do
def __auto_created_at=(value)
raise ::ActiveRecord::ActiveRecordError, '__auto_created_at is read-only'
end
end
end
end
end
end
end
In other words: when this module is included in a base class, make it so that any sub class that inherits from that base
class has this method, __auto_created_at=
defined as an instance method on that sub class.
So much thanks man.
I use it to inspect the callback chain of my controller.
Not finished yet but it's something that look like :