Best way to hook into class_attribute setter?

I have some class attributes that I’m setting in an ActiveSupport::Concern and would like to hook into class_attribute setters, but is really isn’t acting like I would assume.

Here’s what I did to test. I didn’t create a different controller, because just wanted to try to do as little as possible to test this similarly to how I’m doing in my gem in a clean environment.

rails new spike created new .rvmrc in the spike dir so would autocreate its own gemset and use ruby 1.9.3 (using 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin11.4.0]). cd spike rm public/index.html rails g controller home index edit config/routes.rb to look like this:

Spike::Application.routes.draw do get “home/index” root :to => ‘home#index’ end

Added this to app/controllers/my_spike.rb:

module MySpike
extend ActiveSupport::Concern

included do puts ‘included called’ class_attribute :foobar, instance_writer: true end

module ClassMethods def implement_some_methods(options = {}) puts ‘implement_some_methods called’ include SomeMethods end

def foobar=(val)
  puts 'class method foobar= called'
  super
end

end

module SomeMethods

def foobar=(val)
  puts 'instance method foobar= called'
  super
end

def initialize
  puts 'initialize called'
  self.foobar = 'foo'
  puts "self.foobar=#{self.foobar}"
  self.class.foobar = 'bar'
  puts "self.class.foobar=#{self.class.foobar}"
end

def index
  puts 'index called'
end

end end

And then put this into app/controllers/application_controller.rb:

class HomeController < ApplicationController include MySpike implement_some_methods

def index end end

So when I do: rails s

and browse/curl to http://localhost:3000, I do not see either of my attempts to be called in the console:

$ rails s => Booting WEBrick => Rails 3.2.8 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2012-09-28 10:56:25] INFO WEBrick 1.3.1 [2012-09-28 10:56:25] INFO ruby 1.9.3 (2012-04-20) [x86_64-darwin11.4.0] [2012-09-28 10:56:25] INFO WEBrick::HTTPServer#start: pid=4732 port=3000 included called implement_some_methods called initialize called self.foobar=foo self.class.foobar=bar

Started GET “/” for 127.0.0.1 at 2012-09-28 10:56:30 -0400 Connecting to database specified by database.yml Processing by HomeController#index as HTML Rendered home/index.html.erb (1.6ms) Completed 200 OK in 8ms (Views: 7.9ms | ActiveRecord: 0.0ms) [2012-09-28 10:56:30] WARN Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true

Thanks in advance!

This definitely is at least partially because of the order of execution in ActiveSupport::Concern:

    base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
    if const_defined?("InstanceMethods")
      base.send :include, const_get("InstanceMethods")
      ActiveSupport::Deprecation.warn "The InstanceMethods module inside ActiveSupport::Concern will be " \
        "no longer included automatically. Please define instance methods directly in #{self} instead.", caller
    end
    base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")

But, the following doesn’t use the foobar setter overrides, probably because I have the wrong scope for the include and extend that I’ve now added in the included block:

module MySpike
extend ActiveSupport::Concern

included do puts ‘included called’ class_attribute :foobar, instance_writer: true extend ClassMethodsAfterIncluded include InstanceMethodsAfterIncluded end

module ClassMethodsAfterIncluded def foobar=(val) puts ‘class method foobar= called’ super end end

module InstanceMethodsAfterIncluded def foobar=(val) puts ‘instance method foobar= called’ super end end

module ClassMethods def implement_some_methods(options = {}) puts ‘implement_some_methods called’ include SomeMethods end end

module SomeMethods # note: copied from comments below def initialize puts ‘initialize called’ self.foobar = ‘foo’ puts “self.foobar=#{self.foobar}” self.class.foobar = ‘bar’ puts “self.class.foobar=#{self.class.foobar}” end

def index
  puts 'index called'
end

end

end