{"id":14893023,"url":"https://github.com/userlist/active_model-relation","last_synced_at":"2025-11-17T03:31:26.836Z","repository":{"id":256179764,"uuid":"849808401","full_name":"userlist/active_model-relation","owner":"userlist","description":"Query collections of ActiveModel objects like an ActiveRecord::Relation","archived":false,"fork":false,"pushed_at":"2024-11-19T19:24:15.000Z","size":98,"stargazers_count":44,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T19:42:14.478Z","etag":null,"topics":["active-model","rails","ruby"],"latest_commit_sha":null,"homepage":"https://github.com/userlist/active_model-relation","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/userlist.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":"2024-08-30T09:37:58.000Z","updated_at":"2025-01-09T09:31:54.000Z","dependencies_parsed_at":"2024-09-22T05:04:45.767Z","dependency_job_id":"02c89201-fae4-45e2-a13f-1010b4d62803","html_url":"https://github.com/userlist/active_model-relation","commit_stats":{"total_commits":46,"total_committers":1,"mean_commits":46.0,"dds":0.0,"last_synced_commit":"c02b08e8740dab8f2a3f3702b461ba4b15889e07"},"previous_names":["userlist/active_model-relation"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/userlist%2Factive_model-relation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/userlist%2Factive_model-relation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/userlist%2Factive_model-relation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/userlist%2Factive_model-relation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/userlist","download_url":"https://codeload.github.com/userlist/active_model-relation/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248036067,"owners_count":21037092,"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":["active-model","rails","ruby"],"created_at":"2024-09-22T05:01:14.111Z","updated_at":"2025-11-17T03:31:21.798Z","avatar_url":"https://github.com/userlist.png","language":"Ruby","readme":"# ActiveModel::Relation\n\nQuery a collection of ActiveModel objects like an ActiveRecord::Relation.\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add active_model-relation\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install active_model-relation\n\n## Usage\n\n### Initialization\n\nCreate a new relation by passing the model class and a collection:\n\n```ruby\nrelation = ActiveModel::Relation.new(Project, [\n  Project.new(id: 1, state: 'draft', priority: 1),\n  Project.new(id: 2, state: 'running', priority: 2),\n  Project.new(id: 3, state: 'completed', priority: 3),\n  Project.new(id: 4, state: 'completed', priority: 1)\n])\n```\n\nAs an alternative, it's also possible to create a collection for a model without explicitly passing a collection.\nIn this case, the library will attempt to call `Project.records` to get the default collection. If the method doesn't exist or returns `nil`, the collection will default to an empty array.\n\n```ruby\nclass Project\n  def self.records\n    [\n      Project.new(id: 1, state: 'draft', priority: 1),\n      Project.new(id: 2, state: 'running', priority: 2),\n      Project.new(id: 3, state: 'completed', priority: 3),\n      Project.new(id: 4, state: 'completed', priority: 1)\n    ]\n  end\nend\n\nrelation = ActiveModel::Relation.new(Project)\n```\n\n### Querying\n\nAn `ActiveModel::Relation` can be queried almost exactly like an `ActiveRecord::Relation`.\n\n#### `#find`\n\nYou can look up a record by it's primary key, using the `find` method. If no record is found, it will raise a `ActiveModel::RecordNotFound` error.\n\n```ruby\nproject = relation.find(1)\n```\n\nBy default, `ActiveModel::Relation` will assume `:id` as the primary key. You can customize this behavior by setting a `primary_key` on the model class.\n\n```ruby\nclass Project\n  def self.primary_key = :identifier\nend\n```\n\nWhen passed a block, the `find` method will behave like `Enumerable#find`.\n\n```ruby\nproject = relation.find { |p| p.id == 1 }\n```\n\n#### `#find_by`\n\nTo look up a record based on a set of arbitary attributes, you can use `find_by`. It accepts the same arguments as `#where` and will return the first matching record.\n\n```ruby\nproject = relation.find_by(state: 'draft')\n```\n\n#### `#where`\n\nTo filter a relation, you can use `where` and pass a set of attributes and the expected values. This method will return a new `ActiveModel::Relation` that only returns the matching records, so it's possible to chain multiple calls. The filtering will only happen when actually accessing records.\n\n```ruby\nrelation.where(state: 'completed')\n```\n\nThe following two lines will return the same filtered results:\n\n```ruby\nrelation.where(state: 'completed', priority: 1)\nrelation.where(state: 'completed').where(priority: 1)\n```\n\nTo allow for more advanced filtering, `#where` allows filtering using a block. This works similar to `Enumerable#select`, but will return a new `ActiveModel::Relation` instead of an already filtered array.\n\n```ruby\nrelation.where { |p| p.state == 'completed' \u0026\u0026 p.priority == 1 }\n```\n\n#### `#where.not`\n\nSimilar to `#where`, the `#where.not` chain allows you to filter a relation. It will also return a new `ActiveModel::Relation` with that returns only the matching records.\n\n```ruby\nrelation.where.not(state: 'draft')\n```\n\nTo allow for more advanced filtering, `#where.not` allows filtering using a block. This works similar to `Enumerable#reject`, but will return a new `ActiveModel::Relation` instead of an already filtered array.\n\n```ruby\nrelation.where.not { |p| p.state == 'draft' \u0026\u0026 p.priority == 1 }\n```\n\n### Sorting\n\nIt is possible to sort an `ActiveModel::Relation` by a given set of attribute names. Sorting will be applied after filtering, but before limits and offsets.\n\n#### `#order`\n\nTo sort by a single attribute in ascending order, you can just pass the attribute name to the `order` method.\n\n```ruby\nrelation.order(:priority)\n```\n\nTo specify the sort direction, you can pass a hash with the attribute name as key and either `:asc`, or `:desc` as value.\n\n```ruby\nrelation.order(priorty: :desc)\n```\n\nTo order by multiple attributes, you can pass them in the order of specificity you want.\n\n```ruby\nrelation.order(:state, :priority)\n```\n\nFor multiple attributes, it's also possible to specify the direction.\n\n```ruby\nrelation.order(state: :desc, priority: :asc)\n```\n\n### Limiting and offsets\n\n#### `#limit`\n\nTo limit the amount of records returned in the collection, you can call `limit` on the relation. It will return a new `ActiveModel::Relation` that only returns the given limit of records, allowing you to chain multiple other calls. The limit will only be applied when actually accessing the records later on.\n\n```ruby\nrelation.limit(10)\n```\n\n#### `#offset`\n\nTo skip a certain number of records in the collection, you can use `offset` on the relation. It will return a new `ActiveModel::Relation` that skips the given number of records at the beginning. The offset will only be applied when actually accessing the records later on.\n\n```ruby\nrelation.offset(20)\n```\n\n### Scopes\n\nAfter including `ActiveModel::Relation::Model`, the library also supports calling class methods defined on the model class as part of the relation.\n\n```ruby\nclass Project\n  include ActiveModel::Model\n  include ActiveModel::Attributes\n  include ActiveModel::Relation::Model\n\n  attribute :id, :integer\n  attribute :state, :string, default: :draft\n  attribute :priority, :integer, default: 1\n\n  def self.completed\n    where(state: 'completed')\n  end\nend\n```\n\nGiven the example above, you can now create relations like you're used to from `ActiveRecord::Relation`.\n\n```ruby\nprojects = Project.all\ncompleted_projects = all_projects.completed\nimportant_projects = all_projects.where(priority: 1)\n```\n\n### Spawning\n\nIt's possilbe to create new versions of a `ActiveModel::Relation` that only includes certain aspects of the `ActiveModel::Relation` it is based on. It's currently possible to customize the following aspects: `:where`, `:limit`, `:offset`.\n\n#### `#except`\n\nTo create a new `ActiveModel::Relation` without certain aspects, you can use `except` and pass a list of aspects, you'd like to exclude from the newly created instance. The following example will create a new `ActiveModel::Relation` without any previously defined limit or offset.\n\n```ruby\nrelation.except(:limit, :offset)\n```\n#### `#only`\n\nSimilar to `except`, the `only` method will return a new instance of the `ActiveModel::Relation` it is based on but with only the passed list of aspects applied to it.\n\n```ruby\nrelation.only(:where)\n```\n\n### Extending relations\n\n#### `#extending`\n\nIn order to add additional methods to a relation, you can use `extending`. You can either pass a list of modules that will be included in this particular instance, or a block defining additional methods.\n\n```ruby\nmodule Pagination\n  def page_size = 25\n\n  def page(page)\n    limit(page_size).offset(page.to_i * page_size)\n  end\n\n  def total_count\n    except(:limit, :offset).count\n  end\nend\n\nrelation.extending(Pagination)\n```\n\nThe following example is equivalent to the example above:\n\n```ruby\nrelation.extending do\n  def page_size = 25\n\n  def page(page)\n    limit(page_size).offset(page.to_i * page_size)\n  end\n\n  def total_count\n    except(:limit, :offset).count\n  end\nend\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/userlist/active_model-relation. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/userlist/active_model-relation/blob/main/CODE_OF_CONDUCT.md).\n\n## Acknowledgements\n\nThis library is _heavily_ inspired by [`ActiveRecord::Relation`](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/relation.rb) and uses similar patterns and implementations in various parts.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the ActiveModel::Relation project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/userlist/active_model-relation/blob/main/CODE_OF_CONDUCT.md).\n\n## What is Userlist?\n\n[![Userlist](https://userlist.com/images/external/userlist-logo-github.svg)](https://userlist.com/)\n\n[Userlist](https://userlist.com/) allows you to onboard and engage your SaaS users with targeted behavior-based campaigns using email or in-app messages.\n\nUserlist was started in 2017 as an alternative to bulky enterprise messaging tools. We believe that running SaaS products should be more enjoyable. Learn more [about us](https://userlist.com/about-us/).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuserlist%2Factive_model-relation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuserlist%2Factive_model-relation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuserlist%2Factive_model-relation/lists"}