nathanl/authority — pull request #43 by christhekeele (2013-06-14 18:24:24+00:00)
Title: Memoize class attribute reader on Authority::Abilities.authorizer_name.

Authority instructs you to include `Authority::Abilities` into all of your models.

As a rule, all of my models inherit from an `ApplicationModel` that subclasses the ORM I'm using. It handles all boilerplate inclusions, soft deletes, global callback hooks into observers, etc.

Naturally, I tried

class ApplicationModel < ActiveRecord::Base
self.abstract_class = true
include Authority::Abilities
end

to keep thing DRY. However,

module Authority::Abilities
included do |base|
self.authorizer_name = begin
"#{base.name}Authorizer".constantize.name
rescue NameError => e
"ApplicationAuthorizer"
end
end
end

calculates the class_attribute `authorizer_name` upon its inclusion in the model, meaning that without manual settings in every model (just what I was trying to avoid), `authorizer_name` gets set to `'ApplicationModelAuthorizer'` for all subclasses.

In the normal Ruby world, one would work around this as such:

class ApplicationModel < ActiveRecord::Base
self.abstract_class = true
def self.inherited(subclass)
subclass.send :include, Authority::Abilities
end
end

Rails is not the normal Ruby world, however. That approach results in the error

NoMethodError: undefined method `synchronize' for nil:NilClass
from gempath/activerecord-4.0.0.rc2/lib/active_record/attribute_methods.rb:26
in `define_attribute_methods'

when instantiating any model inherited from `ApplicationModel`. This happens because somehow including instance methods triggers `define_attribute_methods` in Rails 4.0.0rc2 before the `attribute_methods_mutex` instance method it references gets populated in AR core.

My fix is to memoize the getter instead of invoking it on inclusion. This pattern should be backwards compatible with all uses of `Authority`, in any framework, and is my weapon of choice for class_attribute defaults since it allows you to potentially set defaults based upon settings in `Authority.configuration` before `Authority.configure` has been invoked.