I need to access an ActiveRecord constant from an included module in a Rails app.
class User < ApplicationRecord
include ModOne
OPTIONS = {a: 1}.freeze
...
end
Module ModOne
extend ActiveSupport::Concern
included do
do_semething(self::OPTIONS)
end
class_methods do
def do_something(opts)
...
end
end
end
But I get
NameError: uninitialized constant User (call 'User.connection' to establish a connection)::OPTIONS Did you mean? User::OPTIONS
What am I missing here?
I have also tried to replace self
with base_class
and event User
but I get the same error.
It's simply a matter of order. You have to define the constant first. Ruby classes are really just a block of code and run top down and when you call include
you're also calling the #included
method on the module.
But a better approach if you want the functionality provided by a module to be customizable is to just write a so called macro method instead of hinging everything on the Module#included
hook:
# You can obscure this with some syntactic sugar from ActiveSupport::Concern
# if it makes you happy.
module Awesomeizer
def foo
self.class.awesomeize_options[:foo]
end
module ClassMethods
def awesomeize_options
@awesomeize_options ||= defaults
end
def awesomeize(**options)
awesomeize_options.merge!(options)
end
def defaults
{}
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
class Thing
include Awesomeizer
awesomeize(foo: :bar)
end
This pattern can be found everywhere in Ruby and is great way to get around the fact that Module#include
doesn't allow you to pass any additional arguments.
In this example the awesomeize
method just stores the options as a class instance variable.
One of the strongest reasons why this is preferable is that it lets you define a signature for the interface between the module and it's consumers instead of just making assumptions. Some gems like Devise even use this method to include it's submodules.