{"id":25874264,"url":"https://github.com/pioz/plucker","last_synced_at":"2025-07-07T19:06:49.813Z","repository":{"id":207754793,"uuid":"720013235","full_name":"pioz/plucker","owner":"pioz","description":"Pluck database records in structs.","archived":false,"fork":false,"pushed_at":"2023-11-20T13:59:51.000Z","size":23,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-02T03:49:31.643Z","etag":null,"topics":["activerecord","database","query","ruby"],"latest_commit_sha":null,"homepage":"","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/pioz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-11-17T11:46:00.000Z","updated_at":"2023-12-09T22:20:27.000Z","dependencies_parsed_at":"2023-11-17T12:50:45.835Z","dependency_job_id":null,"html_url":"https://github.com/pioz/plucker","commit_stats":null,"previous_names":["pioz/plucker"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pioz%2Fplucker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pioz%2Fplucker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pioz%2Fplucker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pioz%2Fplucker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pioz","download_url":"https://codeload.github.com/pioz/plucker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241482708,"owners_count":19969963,"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","database","query","ruby"],"created_at":"2025-03-02T09:19:40.017Z","updated_at":"2025-03-02T09:19:40.557Z","avatar_url":"https://github.com/pioz.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"![build](https://github.com/pioz/plucker/workflows/Ruby/badge.svg)\n[![codecov](https://codecov.io/gh/pioz/plucker/graph/badge.svg?token=95G6SJXB47)](https://codecov.io/gh/pioz/plucker)\n\n# Plucker\n\nPlucker allows projecting records extracted from a query into an array of\nspecifically defined [Ruby structs](https://ruby-doc.org/current/Struct.html) for the occasion. It is an\nenchanted [`pluck`](https://www.rubydoc.info/docs/rails/ActiveRecord%2FCalculations:pluck). It\ntakes a list of values you want to extract and throws them into a custom\narray of Ruby struct.\n\nThis can make your application more efficient because it avoids loading\nActiveRecord objects and utilizes structs, which are more efficient.\n\n## Installation\n\nInstall the gem and add to the application's Gemfile by executing:\n\n    $ bundle add plucker\n\nIf bundler is not being used to manage dependencies, install the gem by executing:\n\n    $ gem install plucker\n\n## Usage\n\n```ruby\nposts = Post.joins(:author, :comments).group(:id).plucker(:title, 'authors.name', { comments_count: 'COUNT(comments.id)' })\npost = posts.first\npost.title # 'How to make pizza'\npost.authors_name # 'Henry'\npost.comments_count # 2\npost.id # NoMethodError: undefined method `id' for #\u003cstruct title=\"How to make pizza\", authors_name=\"Henry\", comments_count=2\u003e\n```\n\n## Purpose\n\nLet's assume we have these classes:\n\n```ruby\nclass Author \u003c ApplicationRecord\n  has_many :post\n\n  validates :name, presence: true\nend\n\nclass Post \u003c ApplicationRecord\n  belongs_to :author\n\n  validates :title, body, presence: true\n\n  def slug\n    self.title.parameterize\n  end\nend\n```\n\nand we execute this query:\n\n```ruby\nposts = Post.joins(:author).select('id, authors.name AS author_name')\n```\n\nThe objects in the posts array are ActiveRecord objects of type `Post`. As I\nread the code, it feels natural for me to be able to do:\n\n```ruby\npost = posts.first\npost.id\npost.title\npost.body\npost.slug\n```\n\nNow, out of these instructions, only `post.id` works, while all the others\nwill result in an error because the fields were not selected. This is very\nstrange to me, and in a complex codebase, it can lead to confusion and\nfrustration.\n\nFurthermore, I can see in the code `post.author_name` and wonder where that\nmethod or column is defined. Obviously, I won't find the definition of that\nmethod because it is dynamically generated by ActiveRecord. I don't like this\nvery much; it makes it unclear what data is present in the object.\n\nTherefore, I have decided to write Plucker to have well-defined objects with\nclear fields right from the start. I aim for lightweight and efficient\nobjects without creating ActiveRecord fat objects with methods that I can't\neven use.\n\nKeep in mind that you can always continue to perform queries in the standard\nActiveRecord way. With Plucker, you have a new, more efficient, and clearer\noption.\n\n## Doc\n\nThe arguments of Plucker can be specified in in 3 different ways depending on\nthe requirements: as a `Symbol`, as a `String`, or as a `Hash`.\n\nWhen using a symbol, the column with the corresponding name to the symbol is\nselected, and the struct field will have that name:\n\n```ruby\npost = Post.plucker(:title).last\n#\u003cstruct title=\"How to make pizza\"\u003e\n\npost = Post.joins(:author).plucker(:title, :name).last\n#\u003cstruct title=\"How to make pizza\", name=\"Henry\"\u003e\n```\n\nWhen using the symbol `:*`, it is interpreted as `SELECT *` statement,\nselecting all columns from the specified table:\n\n```ruby\npost = Post.plucker(:*).last\n#\u003cstruct id=1, title=\"How to make pizza\", author_id=1, created_at=Sat, 21 Oct 2023 14:24:08 UTC +00:00, updated_at=Sat, 21 Oct 2023 14:24:08 UTC +00:00\u003e\n```\n\nWhen using a string value, the name of the struct field will be generated\nusing the [`parameterize`](https://www.rubydoc.info/gems/activesupport/String#parameterize-instance_method)\nfunction with an underscore as the separator:\n\n```ruby\npost = Post.joins(:comments).plucker('posts.title', 'COUNT(comments.id)').last\n#\u003cstruct posts_title=\"How to make pizza\", count_comments_id=2\u003e\n```\n\nWhen using the string `table_name.*`, it is interpreted as `SELECT\ntable_name.*` statement, selecting all columns from the specified table:\n\n```ruby\npost = Post.joins(:author).plucker(:*, 'authors.*').last\n#\u003cstruct id=1, title=\"How to make pizza\", author_id=1, created_at=Sat, 21 Oct 2023 14:24:08 UTC +00:00, updated_at=Sat, 21 Oct 2023 14:24:08 UTC +00:00, authors_id: 1, authors_name: 'Henry', authors_created_at=Sat, 21 Oct 2023 14:24:08 UTC +00:00, authors_updated_at=Sat, 21 Oct 2023 14:24:08 UTC +00:00\u003e\n```\n\nWhen using a Hash, it operates similarly to the String case, except that the\nname of the struct field will be the same as the key of the Hash:\n\n```ruby\npost = Post.joins(:comments).plucker(:title, comments_count: 'COUNT(comments.id)').last\n#\u003cstruct title=\"How to make pizza\", comments_count=2\u003e\n```\n\nPlucker also takes an optional block, which is passed to the struct\ndefinition:\n\n```ruby\nposts = Post.plucker(:title) do\n  def slug\n    self.title.parameterize\n  end\n\n  def as_json\n    super.tap do |json|\n      json['slug'] = self.slug\n    end\n  end\nend.last\n#\u003cstruct title=\"How to make pizza\"\u003e\n\npost.title\n# 'How to make pizza'\npost.slug\n# 'how-to-make-pizza'\npost.as_json\n# {\"title\"=\u003e\"How to make pizza\", \"slug\"=\u003e\"how-to-make-pizza\"}\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/pioz/plucker.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpioz%2Fplucker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpioz%2Fplucker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpioz%2Fplucker/lists"}