{"id":23162753,"url":"https://github.com/pedrorolo/active_module","last_synced_at":"2025-07-15T19:43:17.434Z","repository":{"id":268464762,"uuid":"903502872","full_name":"pedrorolo/active_module","owner":"pedrorolo","description":"Modules and Classes as first-class active record values","archived":false,"fork":false,"pushed_at":"2025-01-20T09:26:35.000Z","size":122,"stargazers_count":7,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-13T01:13:50.547Z","etag":null,"topics":["activemodel","activerecord","configuration","enums","gem","module","modules","prototyping","rapid-prototyping","ruby","ruby-gem","ruby-on-rails","strategy","strategy-pattern"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/active_module","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pedrorolo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-14T19:05:31.000Z","updated_at":"2025-01-23T10:01:15.000Z","dependencies_parsed_at":"2024-12-16T23:52:32.898Z","dependency_job_id":"fd7ca55f-3691-4d87-8d1d-763a0a149add","html_url":"https://github.com/pedrorolo/active_module","commit_stats":null,"previous_names":["pedrorolo/active_module"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/pedrorolo/active_module","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrorolo%2Factive_module","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrorolo%2Factive_module/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrorolo%2Factive_module/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrorolo%2Factive_module/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pedrorolo","download_url":"https://codeload.github.com/pedrorolo/active_module/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pedrorolo%2Factive_module/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265087747,"owners_count":23709384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activemodel","activerecord","configuration","enums","gem","module","modules","prototyping","rapid-prototyping","ruby","ruby-gem","ruby-on-rails","strategy","strategy-pattern"],"created_at":"2024-12-18T00:13:23.601Z","updated_at":"2025-07-15T19:43:17.427Z","avatar_url":"https://github.com/pedrorolo.png","language":"Ruby","readme":"\n# active_module\n[![Gem Version](https://img.shields.io/gem/v/active_module)](https://rubygems.org/gems/active_module)\n[![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)\n[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/pedrorolo/active_module/main.yml)](https://github.com/pedrorolo/active_module/blob/main/Rakefile)\n[![100% Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/pedrorolo/active_module/blob/main/spec/spec_helper.rb)\n[![Gem Total Downloads](https://img.shields.io/gem/dt/active_module?style=flat)](https://bestgems.org/gems/active_module)\n\n\n\n#### *Modules and Classes as first-class active record values!*\n\nActiveModel/ActiveRecord implementation of the Module attribute type.\n\n- Allows storing a reference to a `Module` or `Class` in a `:string` database field\n- Automatically casts strings and symbols into modules when creating and querying objects\n- Symbols or strings refer to the modules using unqualified names\n- It is safe and efficient\n\nThis is a very generic mechanism that enables many possible utilizations, for instance:\n- **Composition-based polymorphism (Strategy design pattern)**\n- **Rapid prototyping static domain objects**\n- **Static configuration management**\n- **Rich Java/C#-like enums**\n\nYou can find examples of these in [Usage -\u003e Examples](#Examples).\n\n## TL;DR\n\nDeclare module attributes like this:\n```ruby\nclass MyARObject \u003c ActiveRecord::Base\n  attribute :module_field, \n            :active_module, \n            possible_modules: [MyModule1, MyClass, Nested::Module]\nend\n```\n\nAssign them like this:\n```ruby \nobject.module_field = Nested::Module\nobject.module_field = :Module\nobject.module_field = \"Module\"\nobject.module_field #=\u003e Nested::Module:Module\n```\n\nQuery them like this:\n```ruby \nMyARObject.where(module_field: Nested::Module)\nMyARObject.where(module_field: :Module)\nMyARObject.where(module_field: \"Module\")\nobject.module_field #=\u003e Nested::Module:Module\n```\n\nAnd compare them like this:\n\n```ruby \nobject.module_field == Nested::Module\n\nmodule MyNameSpace\n  using ActiveModule::Comparison\n\n  object.module_field =~ :Module\n  object.module_field =~ \"Module\"\nend\n```\n\n## Installation\n\nAdd to your gemfile - and if you are using rails - that's all you need:\n\n```ruby\ngem 'active_module', \"~\u003e 0.6\"\n```\n\nIf you are not using rails, just issue this command after loading active record\n\n```ruby\nActiveModule.register!\n```\n\nor this, if you prefer to have a better idea of what you are doing:\n\n```ruby\nActiveModel::Type.register(:active_module, ActiveModule::Base)\nActiveRecord::Type.register(:active_module, ActiveModule::Base)\n```\n\n\n## Usage\n\nAdd a string field to the table you want to hold a module attribute in your migrations\n```ruby\ncreate_table :my_ar_objects do |t|\n  t.string :module_field, index: true\nend\n```\n\nNow given this random module hierarchy:\n```ruby\nclass MyARObject \u003c ActiveRecord::Base\n  module MyModule1; end\n  module MyModule2; end\n  class MyClass; \n    module MyModule1; end\n  end\nend\n```\nYou can make the field refer to one of these modules/classes like this:\n```ruby\nclass MyARObject \u003c ActiveRecord::Base\n  attribute :module_field, \n            :active_module, \n            possible_modules: [MyModule1, MyModule2, MyClass, MyClass::MyModule1]\nend\n```\n\nOptionally, you can specify how to map your modules into the database \n(the default is the module's fully qualified name):\n```ruby\nattribute :module_field, \n          :active_module, \n          possible_modules: [MyModule1, MyModule2, MyClass, MyClass::MyModule1]\n          mapping: {MyModule1 =\u003e \"this is the db representation of module1\"}\n```\n\nAnd this is it! Easy!\u003cbr\u003e\n\n### Assigning and querying module attributes\nNow you can use this attribute in many handy ways!\n\u003cbr\u003e\n\u003cbr\u003e\nFor instance, you may refer to it using module literals:\n```ruby\nMyARObject.create!(module_field: MyARObject::MyModule1)\n\nMyARObject.where(module_field: MyARObject::MyModule1)\n\nmy_ar_object.module_field = MyARObject::MyModule1\n\nmy_ar_object.module_field #=\u003e MyARObject::MyModule1:Module\n\n```\nBut as typing fully qualified module names is not very ergonomic, you may also use symbols instead:\n\n```ruby\nMyARObject.create!(module_field: :MyClass)\n\nMyARObject.where(module_field: :MyClass)\n\nmy_ar_object.module_field = :MyClass\n\nmy_ar_object.module_field #=\u003e MyARObject::MyClass:Class\n\n```\n\nHowever, if there is the need for disambiguation, you can always use strings instead:\n\n```ruby\n\nMyARObject.create!(module_field: \"MyClass::MyModule1\")\n\nMyARObject.where(module_field: \"MyClass::MyModule1\")\n\nmy_ar_object.module_field = \"MyClass::MyModule1\"\n\nmy_ar_object.module_field #=\u003e MyARObject::MyClass::MyModule::Module\n```\n\n### Comparing modules with strings and symbols\n\nIn order to compare modules with Strings or Symbols you'll have to use the `ActiveModule::Comparison`\nrefinement. This refinement adds the method `Module#=~` to the `Module` class, but this change is\nonly available within the namespace that includes the refinement.\n\n```ruby\nmodule YourClassOrModuleThatWantsToCompare\n  using ActiveModule::Comparison\n\n  def method_that_compares\n    my_ar_object.module_field =~ :MyModule1\n  end\nend\n```\n\nor like this, if you don't want to use the refinement:\n\n```ruby\nActiveModule::Comparison.compare(my_ar_object.module_field, :MyModule1)\n```\n\nbut in this last case it would probably make more sense to simply use a module literal:\n\n```ruby\nmy_ar_object.module_field == MyClass::MyModule1\n```\n\n\n## Examples\n\n### Composition-based polymorphism (Strategy design pattern)\n\n[The Strategy design pattern](https://en.wikipedia.org/wiki/Strategy_pattern) allows composition based polymorphism. This enables runtime polymorphism (by changing the strategy in runtime), \nand multiple-polymorphism (by composing an object of multiple strategies).\n\nIf you want to use classes this will do: \n```ruby\nclass MyARObject \u003c ActiveRecord::Base\n  attribute :strategy_class, :active_module, possible_modules: StrategySuperclass.subclasses\n\n  def strategy\n    @strategy ||= strategy_class.new(some_args_from_the_instance)\n  end\n\n  def run_strategy!(args)\n    strategy.call(args)\n  end\nend\n```\n\nBut if you are not in the mood to define a class hierarchy for it (or if you are performance-savy),\nyou may use modules instead:\n\n```ruby\nclass MyARObject \u003c ActiveRecord::Base\n  module Strategy1\n    def self.call\n      \"strategy1 called\"\n    end\n  end\n\n  module Strategy2\n    def self.call\n      \"strategy2 called\"\n    end\n  end\n\n  attribute :strategy, \n            :active_module, \n            possible_modules: [Strategy1, Strategy2]\n\n  def run_strategy!(some_args)\n    strategy.call(some_args, other_args)\n  end\nend\n\nMyARObject.create!(module_field: :Strategy1).run_strategy! #=\u003e \"strategy1 called\"\nMyARObject.create!(module_field: :Strategy2).run_strategy! #=\u003e \"strategy2 called\"\n```\n\nYou can later easily promote these modules to classes if you need instance variables:\n\n```ruby\nclass MyARObject \u003c ActiveRecord::Base\n  class Strategy1\n    def self.call\n      self.new.call\n    end\n\n    def call\n      \"strategy1 called\"\n    end\n  end\n\n  module Strategy2\n    def self.call\n      \"strategy2 called\"\n    end\n  end\n\n  attribute :strategy, \n            :active_module, \n            possible_modules: [Strategy1, Strategy2]\n\n  def run_strategy!(some_args)\n    strategy.call(some_args, other_args)\n  end\nend\n\nMyARObject.create!(module_field: :Strategy1).run_strategy! #=\u003e \"strategy1 called\"\nMyARObject.create!(module_field: :Strategy2).run_strategy! #=\u003e \"strategy2 called\"\n```\n\n\n### Rapid prototyping static domain objects\n\n```ruby \n# Provider domain Object\nmodule Provider\n # As if the domain model class\n  def self.all\n    [Ebay, Amazon]\n  end\n\n  # As if the domain model instances\n  module Ebay\n    def self.do_something!\n      \"do something with the ebay provider config\"\n    end\n  end\n\n  module Amazon\n    def self.do_something!\n      \"do something with the amazon provider config\"\n    end\n  end\nend\n\nclass MyARObject \u003c ActiveRecord::Base\n  attribute :provider, \n            :active_module, \n            possible_modules: Provider.all\nend\n\nMyARObject.create!(provider: :Ebay).provier.do_something! \n  #=\u003e \"do something with the ebay provider config\"\nMyARObject.create!(provider: Provider::Amazon).provider.do_something! \n  #=\u003e \"do something with the amazon provider config\"\n```\n\nWhat is interesting about this is that we can later easily promote\nour provider objects into full fledged ActiveRecord objects without \nbig changes to our code:\n```ruby\nclass Provider \u003c ActiveRecord::Base\n  def do_something!\n    #...\n  end\nend\n\nclass MyARObject \u003c ActiveRecord::Base\n  belongs_to :provider\nend\n```\n\nJust in case you'd like to have shared code amongst the instances in the above example, \nthis is how you could do so:\n\n```ruby \n# Provider domain Object\nmodule Provider\n  # As if the domain model class\n  def self.all\n    [Ebay, Amazon]\n  end\n\n  module Base \n    def do_something!\n      \"do something with #{something_from_an_instance}\"\n    end\n  end\n\n  # As if the domain model instances\n  module Ebay\n    include Base\n    extend self\n\n    def something_from_an_instance\n      \"the ebay provider config\"\n    end\n  end\n\n  module Amazon\n    include Base\n    extend self\n\n    def something_from_an_instance\n      \"the amazon provider config\"\n    end\n  end\nend\n```\n\n\n### Static configuration management\n\nThis example is not much different than previous one. It however stresses that the module we\nrefer to might be used as a source of configuration parameters that change the behaviour of \nthe class it belongs to:\n\n```ruby \n# Provider domain Object\nmodule ProviderConfig\n  module Ebay\n    module_function\n\n    def url= 'www.ebay.com'\n    def number_of_attempts= 5 \n  end\n\n  module Amazon\n    module_function\n\n    def url= 'www.amazon.com'\n    def number_of_attempts= 10\n  end\n\n  def self.all\n    [Ebay, Amazon]\n  end\nend\n\nclass MyARObject \u003c ActiveRecord::Base\n  attribute :provider_config, \n            :active_module, \n            possible_modules: ProviderConfig.all\n\n  def load_page!\n    n_attempts = 0\n    result = nil\n    while n_attempts \u003c provider.number_of_attempts\n      result = get_page(provider.url)\n      if(result)\n        return result\n      else\n        n_attempts.inc\n      end\n    end\n    result\n  end\nend\n\nMyARObject.create!(provider_config: :Ebay).load_page!\n```\n\n### Rich Java/C#-like enums\nThis example is only to show the possibility. \nThis would probably benefit from using a meta programming abstraction and we will provide something\nlike that in the future.\n\nJava/C# enums allow defining methods on the enum, which are shared across all enum values:\n\n```ruby\nmodule PipelineStage\n  module_function\n\n  def all\n    [InitialContact, InNegotiations, LostDeal, PaidOut]\n  end\n\n  def cast(stage)\n    self.all.map(\u0026:external_provider_code).find{|code| code == stage} ||\n    self.all.map(\u0026:database_representation).find{|code| code == stage} ||\n    self.all.map(\u0026:frontend_representation).find{|code| code == stage} \n  end\n\n  module Base\n    def external_provider_code\n      @external_provider_code ||= self.name.underscore\n    end\n\n    def database_representation\n      self.name\n    end\n\n    def frontend_representation\n      @frontend_representation ||= self.name.demodulize.upcase\n    end\n  end\n\n  module InitialContact\n    extend Base\n  end\n\n  module InNegotiations\n    extend Base\n  end\n\n  module LostDeal\n    extend Base\n  end\n\n  module PaidOut\n    extend Base\n  end\nend\n\nclass MyARObject \u003c ActiveRecord::Base\n  attribute :pipeline_stage, \n            :active_module, \n            possible_modules: PipelineStage.all\nend\n\nobject = MyARObject.new(pipeline_stage: :InitialStage)\nobject.pipeline_stage\u0026.frontend_representation #=\u003e \"INITIAL_STAGE\"\nobject.pipeline_stage = :InNegotiations\nobject.pipeline_stage\u0026.database_representation #=\u003e \"PipelineStage::InNegotiations\"\n```\n\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/pedrorolo/active_module.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedrorolo%2Factive_module","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpedrorolo%2Factive_module","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpedrorolo%2Factive_module/lists"}