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.
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.
1 Answer
Reset to default 4It'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.
OPTIONS
needs to be above theinclude
– Alex Commented Feb 6 at 15:57include
it is not yet defined. order matters. – Alex Commented Feb 6 at 16:03