{"id":23799363,"url":"https://github.com/t27duck/has_config","last_synced_at":"2025-10-08T17:16:37.453Z","repository":{"id":34745238,"uuid":"38724994","full_name":"t27duck/has_config","owner":"t27duck","description":"Quick record-specific configuration for your models","archived":false,"fork":false,"pushed_at":"2025-04-09T01:24:37.000Z","size":78,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-10T19:34:42.210Z","etag":null,"topics":["activerecord","rails","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"LambdaSchool/UI-III-Flexbox","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/t27duck.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2015-07-08T01:42:11.000Z","updated_at":"2025-04-09T01:24:40.000Z","dependencies_parsed_at":"2023-12-26T18:46:00.666Z","dependency_job_id":null,"html_url":"https://github.com/t27duck/has_config","commit_stats":{"total_commits":70,"total_committers":1,"mean_commits":70.0,"dds":0.0,"last_synced_commit":"7dd14035b45602e22851bacef60c3d21453f14de"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/t27duck/has_config","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t27duck%2Fhas_config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t27duck%2Fhas_config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t27duck%2Fhas_config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t27duck%2Fhas_config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/t27duck","download_url":"https://codeload.github.com/t27duck/has_config/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/t27duck%2Fhas_config/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260769459,"owners_count":23060105,"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":["activerecord","rails","ruby"],"created_at":"2025-01-01T21:15:19.103Z","updated_at":"2025-10-08T17:16:32.419Z","avatar_url":"https://github.com/t27duck.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HasConfig\n\n![Build Status](https://github.com/t27duck/has_config/workflows/CI/badge.svg)\n\nWhen working with models in a large Rails project, you sometimes end up with \"god objects\" which start to be loaded down with several booleans, integers, and strings from select boxes that act as configuration options. As time goes on, you add more and more columns. As your database and user-base grows, adding even a single column more can bring your app to a hang during a deploy due to table locking or a slew of exceptions due to [issues and gotchas like this](https://github.com/rails/rails/issues/12330).\n\nIn an attempt to cut down on cluttering your model with boolean columns, `has_config` allows you to have a single column contain all configuration switches you could ever want. Adding another configuration option to a model no longer requires a migration to add a column. You can also continue writing code as if the model had all of those individual attributes.\n\n## Requirements\n\nSupported Rubies: 3.0 and later\n\nSupported versions of ActiveRecord: 6.1 - 7.1\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'has_config'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install has_config\n\n## Usage\n\nLet's say we have a model called `Client` whose job is to hold the general information configuration for a client in a multi-tenant application. First, we need to add one column to the model to hold the configuration information. By default, the gem assumes the column's name is `configuration`, but you can change that (more on that later).\n\n```ruby\nclass AddConfigurationToClients \u003c ActiveRecord::Migration\n  add_column :clients, :configuration, :text\nend\n```\n\nWe now want to make that column a serialized hash in our model and include the `HasConfig::ActiveReocrd::ModelAdapter` module.\n\n```ruby\nclass Client \u003c ActiveRecord::Base\n  serialize :configuration, Hash\n  include HasConfig::ActiveRecord::ModelAdapter\nend\n```\n\nIf you are using PostgreSQL 9.2 or later, you can use the JSON or JSONB (if using Rails 4.2 or later) data-type for the configuration column and not have to declare it as a serilaized attribute in the model as `ActiveRecord` will take care of that for you.\n\nIf you want to use a different column name, you may override the default by setting `self.has_config_configuration_column = 'other_column_name'` in the model.\n\n```ruby\nclass Client \u003c ActiveRecord::Base\n  serialize :configuration, Hash\n  include HasConfig::ActiveRecord::ModelAdapter\n  has_config :primary_color, config: { type: :string, default: 'green' }\n  has_config :secondary_color, config: { type: :string }\n  has_config :rate_limit, config: { type: :integer, validations: { numericality: { only_integer: true } } }\n  has_config :category, config: { type: :string, validations: { inclusion: { in: CATEGORIES } } }\n  has_config :active, config: { type: :boolean, default: false }\nend\n```\nThe `has_config` method is the primary interface for adding a setting to a model. The first argument is a symbol that represents the name of the setting.\n\nThe `config` key is a hash that contains information describing your setting. The `type` is the only required key when including the `config` option.\n\n`type` is the datatype of your setting. Valid options are `string`, `integer`, and `boolean`.\n\n`default` is the value that will be used if the record does not have this setting set. If no `default` is provided, `nil` will be used.\n\n`validations` allows the setting to use the standard ActiveRecord validations you'd use for any regular attribute.\n\nOk, still with me? Back to our example...\n\nHere, the `Client` model has five configuration items on it: `primary_color`, `secondary_color`, `rate_limit`, `category`, and `active`. So, knowing what you just learned above...\n\n`primary_color` is a string with a default value of \"green\".\n\n`secondary_color` is a string without a default.\n\n`rate_limit` is an integer that validates its value is in fact, an integer.\n\n`category` is a string that must be a value in the array `CATEGORIES`.\n\n`active` is a boolean value with a default of `false`.\n\nWe can now access these configuration settings as if they were regular attributes on the model:\n\n```irb\nclient = Client.new\nclient.default_color\n=\u003e \"green\"\nclient.secondary_color\n=\u003e nil\nclient.active\n=\u003e false\nclient.active?\n=\u003e false\nclient.active = '1' # Like if this was submitted from a form\n=\u003e '1'\nclient.active?\n=\u003e true\nclient.rate_limit = 3\n=\u003e 3\nclient.valid?\n=\u003e false\nclient.errors.full_messages\n=\u003e [\"Category is not in the list\"]\n```\n\nEverything acts pretty much as you'd expect it too do. Configurations that fail validations make the record invalid. Passing in '1', 'true', `true`, etc casts boolean values. Passing in an empty string for an integer config casts as `nil`.\n\n## Chaining with other models with the same setting\n\nLet's say you have a `Client` model, a `Group` model, and a `User` model. A client has many groups and a group can have many users. A client can have configuration which globally affects all users; however, a group setting of the same name could override the global setting. HasConfig can handle this with relative ease.\n\nFirst, let's set up the models\n\n```ruby\nclass Client \u003c ActiveRecord::Base\n  has_many :groups\n  # ...\n  has_config :some_setting, config: { type: :integer, default: 3 }\nend\n\n\nclass Group \u003c ActiveRecord::Base\n  belongs_to :client\n  has_many :users\n\n  # ...\n  has_config :some_setting, config: { type: :integer }, parent: :client\nend\n````\n\nThis introduces a new option for the `has_config` method: `parent`. The `parent` option specifies a method `HasConfig` can use to defer the setting value to another object.\n\nAssume we have a client and a group stored in our database:\n\n```irb\ng = Group.first\n=\u003e #\u003cGroup ...\u003e\ng.client\n=\u003e \u003c#Client ...\u003e\ng.some_setting\n=\u003e nil\ng.some_setting(:resolve)\n=\u003e 3\ng.some_setting = 1\n=\u003e 1\ng.some_setting(:resolve)\n=\u003e 1\ng.some_setting = nil\n=\u003e nil\ng.some_setting(:resolve)\n=\u003e 3\n```\n\nSee what happened? Note the subtle change in how we reference the stting?\n\nWhen we pass the symbol `:resolve` into the setting's getter method, and is blank, we will defer to the setting in the parent (in this case, `Client`) and use that value. If you do not pass `:resolve` in the getter, the local value will be used.\n\nBy default, `HasConfig` will go up the chain if the child model's value is `blank` (from `ActiveSupport`'s `blank?` method).\n\nYou can chain as deep as you want as long as the object returned from `parent` includes a setting of the same name as the child. Meaning, your `User` model can chain `some_setting` up to `group` which can chain up to `client`.\n\nYou do have some control over when `HasConfig` invokes the change via the `chain_on` option for the setting's config:\n\n```ruby\n# Chain will be invoked if the local value is `nil`\nhas_config :setting1, config: { type: :string, chain_on: :nil }, parent: :some_method\n\n# Chain will be invoked if the local value is `false`\nhas_config :setting2, config: { type: :string, chain_on: :false }, parent: :some_other_method\n```\n\n## Configuration file\n\nAn alternative to defining the definition of each setting in your model is to put them in a centralized configuration file.\n\nGiving a file located at `#{Rails.root}/config/has_config.rb`:\n\n```ruby\nhas_config :primary_color, config: { type: :string, default: 'green' }\nhas_config :secondary_color, config: { type: :string }\nhas_config :rate_limit, config: { type: :integer, validations: { numericality: { only_integer: true } } }\nhas_config :category, config: { :string, validations: { inclusion: { in: CATEGORIES } } }\nhas_config :active, config: { type: :boolean, default: false }\n````\n\n... and then somewhere in your app, call `HasConfig::Engine.load` (There's an optional `path:` argument to specify a different file path)\n\nThis will load up pre-configured setting information in your app. You can then just refer to each setting by name in your model:\n\n```ruby\nclass Client \u003c ActiveRecord::Base\n  serialize :configuration, Hash\n  include HasConfig::ActiveRecord::ModelAdapter\n  has_config :primary_color\n  has_config :secondary_color\n  has_config :rate_limit\n  has_config :category\n  has_config :active\nend\n```\n\nYou can also override the `default` and `validations` options for a pre-defined config:\n\n```ruby\nhas_config :primary_color, config: { default: 'custom_value_unique_to_this_model' }\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` 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 tags, 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/t27duck/has_config.\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft27duck%2Fhas_config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ft27duck%2Fhas_config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ft27duck%2Fhas_config/lists"}