# ActiveModule: A Module attribute type implementation

**URL:** <https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218>\
**Category:** rubyonrails-talk\
**Created:** [December 16, 2024, 10:30pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218 "2024-12-16T22:30:22Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![pedrorolo](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/pedrorolo/32/12948_2.png) [@pedrorolo](https://discuss.rubyonrails.org/u/pedrorolo)\
**Post date:** [December 16, 2024, 10:30pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/1 "2024-12-16T22:30:22Z")

</div>

I have just published a little gem that allows to store references to classes and modules in active record attributes. I am also thinking of contributing this code to rails-core. (I just need to port the tests from rspec and move the README into the rails docs)

Any comments are welcome: [GitHub - pedrorolo/active\_module: ActiveModel/ActiveRecord module attribute type implementation](https://github.com/pedrorolo/active_module)

---

<div class="post-metadata">

**Author:** ![Jrodriigues](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/jrodriigues/32/11773_2.png) [@Jrodriigues](https://discuss.rubyonrails.org/u/Jrodriigues)\
**Post date:** [December 17, 2024, 11:51am UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/2 "2024-12-17T11:51:20Z")

</div>

Interesting, looking forward to give a try.

---

<div class="post-metadata">

**Author:** ![Henrik\_N](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/henrik_n/32/12866_2.png) [@Henrik\_N](https://discuss.rubyonrails.org/u/Henrik_N)\
**Post date:** [December 22, 2024, 8:07am UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/3 "2024-12-22T08:07:57Z")

</div>

Fun idea!

After a quick look at the README docs, one concern is that it only documents assigning a list of `possible_modules` as module references at class-loading time.

This could cause circular dependency issues if you e.g. have

```ruby
class A < ActiveRecord::Base
  attribute :x, :active_module, possible_modules: [B]
end

class B < ActiveRecord::Base
  attribute :x, :active_module, possible_modules: [A]
end

```

Might be a good idea to (document that you) support strings and/or lambdas.

Cf. `class_name: "SomeString"` in Active Record.

---

<div class="post-metadata">

**Author:** ![pedrorolo](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/pedrorolo/32/12948_2.png) [@pedrorolo](https://discuss.rubyonrails.org/u/pedrorolo)\
**Post date:** [December 22, 2024, 8:48am UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/4 "2024-12-22T08:48:31Z")

</div>

Hi Henrik,

Thank you for the heads up!

I will experiment to check if I can find any issues with circular dependencies. My intuition tells me that those will not be an issue: that Zeitwerk will manage those problems for us. However, rails developers do indeed resort to `class_name` for associations, and I must confess I don’t fully understand their motivation for doing so. Let’s hope I don’t have the same problem to sort out…

---

<div class="post-metadata">

**Author:** ![Henrik\_N](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/henrik_n/32/12866_2.png) [@Henrik\_N](https://discuss.rubyonrails.org/u/Henrik_N)\
**Post date:** [December 22, 2024, 6:12pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/5 "2024-12-22T18:12:02Z")

</div>

I haven’t looked into circular dependencies in Ruby for a long time, so I tried it out now.

This example runs, but outputs a warning.

I would be surprised if Zeitwerk changes anything about this, other than not having to write the explicit `require_relative`s.

```ruby
# a.rb
class A
  require_relative "b"
  def say = puts "hi A"
end

# b.rb
class B
  require_relative "a"
  def say = puts "hi B"
end

# runner.rb
require_relative "a"
require_relative "b"
A.new.say
B.new.say

```

```bash
ruby -w runner.rb

/private/tmp/foo/b.rb:2: warning: /private/tmp/foo/b.rb:2: warning: loading in progress, circular require considered harmful - /private/tmp/foo/a.rb
…

hi A
hi B

```

---

<div class="post-metadata">

**Author:** ![pedrorolo](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/pedrorolo/32/12948_2.png) [@pedrorolo](https://discuss.rubyonrails.org/u/pedrorolo)\
**Post date:** [December 22, 2024, 8:51pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/6 "2024-12-22T20:51:24Z")

</div>

I acknowledge your example does indeed answer with that warning. However, I did not get any kind of warning when I trying to replicate the same problem in a rails application using your former example pertaining the active\_module gem, either in dev or in production mode:

```ruby
# models/a.rb
class A < ApplicationRecord
  attribute :field, :active_module, possible_modules: [B]
end

#models/b.rb
class B < ApplicationRecord
  attribute :field, :active_module, possible_modules: [A]
end

#controllers/test_controller.rb
class TestController < ApplicationController
  def index
    A.new(field: B)
    B.new(field: A)
    render plain: 'Hello, Banana'
  end
end

```

```ruby
─▪ rails s -e production
=> Booting Puma
=> Rails 7.1.5.1 application starting in production 
=> Run `bin/rails server --help` for more startup options
W, [2024-12-22T20:36:11.417528 #88583] WARN -- : You are running SQLite in production, this is generally not recommended. You can disable this warning by setting "config.active_record.sqlite3_production_warning=false".
Puma starting in single mode...
* Puma version: 6.5.0 ("Sky's Version")
* Ruby version: ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [arm64-darwin23]
* Min threads: 5
* Max threads: 5
* Environment: production
* PID: 88583
* Listening on http://0.0.0.0:3000
Use Ctrl-C to stop
I, [2024-12-22T20:36:13.531121 #88583] INFO -- : [83bb7c72-0865-4333-b4df-4d9db5344483] Started GET "/" for 127.0.0.1 at 2024-12-22 20:36:13 +0000
I, [2024-12-22T20:36:13.534898 #88583] INFO -- : [83bb7c72-0865-4333-b4df-4d9db5344483] Processing by TestController#index as HTML
I, [2024-12-22T20:36:13.556391 #88583] INFO -- : [83bb7c72-0865-4333-b4df-4d9db5344483] Completed 200 OK in 21ms (Views: 0.9ms | ActiveRecord: 2.5ms | Allocations: 8745)

```

From this little test I am assuming that Rails is somehow properly handling this problem. If somebody can confirm me that this is an issue please let me know.

However, when launching your little example with the a.rb; b.rb, using zeitwerk I do indeed get the error your are talking about

```ruby
┌─[tmp][]
└─▪ cat a.rb
require_relative 'b'

class A
  def say
    "say a"
  end
end
┌─[tmp][]
└─▪ cat b.rb
require_relative 'a'

class B
  def say
    "say b"
  end
end
┌─[tmp][]
└─▪ cat runner.rb 
# runner.rb
require 'zeitwerk'
loader = Zeitwerk::Loader.new
loader.push_dir("./")
loader.setup # ready!
A.new.say
B.new.say
┌─[tmp][]
└─▪ ruby -w runner.rb 
/Users/pedrorolo/tmp/b.rb:1: warning: /Users/pedrorolo/tmp/b.rb:1: warning: loading in progress, circular require considered harmful - /Users/pedrorolo/tmp/a.rb
        from runner.rb:6:in `<main>'
        from /Users/pedrorolo/.rvm/gems/ruby-3.2.3/gems/zeitwerk-2.7.1/lib/zeitwerk/core_ext/kernel.rb:26:in `require'
        from <internal:/Users/pedrorolo/.rvm/rubies/ruby-3.2.3/lib/ruby/site_ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:136:in `require'
        from <internal:/Users/pedrorolo/.rvm/rubies/ruby-3.2.3/lib/ruby/site_ruby/3.2.0/rubygems/core_ext/kernel_require.rb>:136:in `require'
        from /Users/pedrorolo/tmp/a.rb:1:in `<top (required)>'
        from /Users/pedrorolo/tmp/a.rb:1:in `require_relative'
        from /Users/pedrorolo/tmp/b.rb:1:in `<top (required)>'
        from /Users/pedrorolo/tmp/b.rb:1:in `require_relative'

```

Anyway, if a AR model needs the referenced module to refer to itself, it can always inject itself or pass itself as argument, thus circunventing that problem:

```ruby
class ARClass < ActiveRecord::Base
  attribute :field, :active_module, possible_modules: [B]

  def run_field
     field.call(self)
     #or
     field.new(self).call
  end
end
```

---

<div class="post-metadata">

**Author:** ![pedrorolo](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/pedrorolo/32/12948_2.png) [@pedrorolo](https://discuss.rubyonrails.org/u/pedrorolo)\
**Post date:** [December 22, 2024, 9:10pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/7 "2024-12-22T21:10:25Z")

</div>

I had forgoten to remove the requires from the a and b files! Zeitwerk does indeed deal well with this kind of circular dependencies:

```ruby
┌─[tmp][]
└─▪ cat a.rb 
class A
  B.new
  def say
    puts "say a"
  end

  def say_b
    B.new.say
  end
end
┌─[tmp][]
└─▪ cat b.rb 
class B
  A.new
  def say
    puts "say b"
  end

  def say_a
    A.new.say
  end
end
┌─[tmp][]
└─▪ cat runner.rb 
# runner.rb
require 'zeitwerk'
loader = Zeitwerk::Loader.new
loader.push_dir("~/tmp")
loader.setup # ready!
A.new.say
B.new.say
┌─[tmp][]
└─▪ ruby -w runner.rb 
say a
say b
┌─[tmp][]
└─▪ 

```

---

<div class="post-metadata">

**Author:** ![Henrik\_N](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/henrik_n/32/12866_2.png) [@Henrik\_N](https://discuss.rubyonrails.org/u/Henrik_N)\
**Post date:** [December 22, 2024, 9:35pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/8 "2024-12-22T21:35:31Z")

</div>

Ah, interesting! I did not think Zeitwerk changed anything regarding circular dependencies, but I see it does. Their README [docs](https://github.com/fxn/zeitwerk?tab=readme-ov-file#beware-of-circular-dependencies) a more complex kind of circular dependency as something to avoid, which seems to implicitly suggest that simpler kinds of circular dependencies are not seen as a problem.

(cc @fxn – would be super interesting to hear more about this if you’re willing.)

---

<div class="post-metadata">

**Author:** ![Henrik\_N](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/henrik_n/32/12866_2.png) [@Henrik\_N](https://discuss.rubyonrails.org/u/Henrik_N)\
**Post date:** [December 22, 2024, 9:45pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/9 "2024-12-22T21:45:24Z")

</div>

After further thought, I guess there’s nothing special going on in the Zeitwerk case – it’s probably just that because it knows that A has already been required, it doesn’t attempt to require it again when B references it, so no warning is triggered. Same as this situation without Zeitwerk:

```ruby
# a.rb
class A
  require_relative "b"
  B.name
  def say = puts "hi A"
end

# b.rb
class B
  A.name
  def say = puts "hi B"
end

# runner.rb
require_relative "a"

A.new.say
B.new.say

```

```bash
$ ruby -w runner.rb
hi A
hi B

```

I think in summary, you’d get to a place where you don’t have this warning (which is nice) but you are of course still at theoretical risk of confusing situations if the circular dependencies get more complex. In many cases, it will be fine.

---

<div class="post-metadata">

**Author:** ![fxn](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/fxn/32/12956_2.png) [@fxn](https://discuss.rubyonrails.org/u/fxn)\
**Post date:** [December 22, 2024, 11:08pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/10 "2024-12-22T23:08:58Z")

</div>

Yeah, this works just fine with Zeitwerk, let me explain.

You have to imagine that on setup the loader defines autoloads for `A`, and for `B`, before any of them can be reached by client code. “autoloads” means `Module#autoload` calls for `:A` and `:B` in `Object` pointing to the absolute paths of their respective files.

Now, if you autoload `A` and `B` is referenced in the body of `A`, that is going to trigger the autoload of `B` when that line is hit. The important observation is that since that is happening **in the body of A** , the `A` class is already defined. Partially, but defined. Therefore, when `B` gets autoloaded due to that reference, and there is an `A` in there, since it is already in RAM, it can be resolved like any known constant.

If the body of `B` tried to use a class method of `A` defined at the bottom of its class body, it would fail. But that is, again, standard Ruby logic. You know.

---

<div class="post-metadata">

**Author:** ![pedrorolo](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/pedrorolo/32/12948_2.png) [@pedrorolo](https://discuss.rubyonrails.org/u/pedrorolo)\
**Post date:** [December 30, 2024, 2:58pm UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/11 "2024-12-30T14:58:45Z")

</div>

Well… this little drift towards autoloading made me replace my gem’s manual requires by Zeitwerk! 🙂 [use zeitwerk instead of manual requires · pedrorolo/active\_module@3be7d9d · GitHub](https://github.com/pedrorolo/active_module/commit/3be7d9d8ea0081336b55f3faa7deb96ab798d39f)

---

<div class="post-metadata">

**Author:** ![pedrorolo](https://sea2.discourse-cdn.com/rubyonrails/user_avatar/discuss.rubyonrails.org/pedrorolo/32/12948_2.png) [@pedrorolo](https://discuss.rubyonrails.org/u/pedrorolo)\
**Post date:** [August 26, 2026, 9:10am UTC](https://discuss.rubyonrails.org/t/activemodule-a-module-attribute-type-implementation/88218/12 "2026-08-26T09:10:36Z")

</div>

I have released a new version of this gem. It is now fully compatible with enum, enabling using active\_module fields are drop-in replacements for enums:

```ruby
class MyARObject < ActiveRecord::Base
  attribute :module_field,
            :active_module,
            possible_modules: [MyModule1, MyClass, Nested::Module]

  # optional, just for enum-like behaviour
  active_module_enum :module_field
end

# Assignment

object.module_field = Nested::Module
object.module_field = :Module
object.module_field = "Module"
object.module_field = :nested_module
object.module_field # => Nested::Module

# Querying

MyARObject.where(module_field: Nested::Module)
MyARObject.where(module_field: :Module)
MyARObject.where(module_field: "Module")
MyARObject.where(module_field: :nested_module) # underscored nested name

# Comparision
module MyNameSpace
  using ActiveModule::Comparison

  object.module_field =~ :Module
  object.module_field =~ "Module"
  object.module_field =~ :nested_module # underscored nested name
end

# Enum-like behaviour provided by the active_module_enum invocation

MyARObject.fields # => { MyModule1 => "::MyModule1", MyClass => "::MyClass", Nested::Module => "::MyNestedModule" }

MyARObject.with_my_class.where(..)

object.my_class!

object.my_class?

```

Looking for feedback and comments. Please let me know what do you think about it.@fxn Do you think I should try to contribute this directly to rails?
