An open API service indexing awesome lists of open source software.

https://github.com/pioz/plucker

Pluck database records in structs.
https://github.com/pioz/plucker

activerecord database query ruby

Last synced: 2 months ago
JSON representation

Pluck database records in structs.

Awesome Lists containing this project

README

        

![build](https://github.com/pioz/plucker/workflows/Ruby/badge.svg)
[![codecov](https://codecov.io/gh/pioz/plucker/graph/badge.svg?token=95G6SJXB47)](https://codecov.io/gh/pioz/plucker)

# Plucker

Plucker allows projecting records extracted from a query into an array of
specifically defined [Ruby structs](https://ruby-doc.org/current/Struct.html) for the occasion. It is an
enchanted [`pluck`](https://www.rubydoc.info/docs/rails/ActiveRecord%2FCalculations:pluck). It
takes a list of values you want to extract and throws them into a custom
array of Ruby struct.

This can make your application more efficient because it avoids loading
ActiveRecord objects and utilizes structs, which are more efficient.

## Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add plucker

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install plucker

## Usage

```ruby
posts = Post.joins(:author, :comments).group(:id).plucker(:title, 'authors.name', { comments_count: 'COUNT(comments.id)' })
post = posts.first
post.title # 'How to make pizza'
post.authors_name # 'Henry'
post.comments_count # 2
post.id # NoMethodError: undefined method `id' for #
```

## Purpose

Let's assume we have these classes:

```ruby
class Author < ApplicationRecord
has_many :post

validates :name, presence: true
end

class Post < ApplicationRecord
belongs_to :author

validates :title, body, presence: true

def slug
self.title.parameterize
end
end
```

and we execute this query:

```ruby
posts = Post.joins(:author).select('id, authors.name AS author_name')
```

The objects in the posts array are ActiveRecord objects of type `Post`. As I
read the code, it feels natural for me to be able to do:

```ruby
post = posts.first
post.id
post.title
post.body
post.slug
```

Now, out of these instructions, only `post.id` works, while all the others
will result in an error because the fields were not selected. This is very
strange to me, and in a complex codebase, it can lead to confusion and
frustration.

Furthermore, I can see in the code `post.author_name` and wonder where that
method or column is defined. Obviously, I won't find the definition of that
method because it is dynamically generated by ActiveRecord. I don't like this
very much; it makes it unclear what data is present in the object.

Therefore, I have decided to write Plucker to have well-defined objects with
clear fields right from the start. I aim for lightweight and efficient
objects without creating ActiveRecord fat objects with methods that I can't
even use.

Keep in mind that you can always continue to perform queries in the standard
ActiveRecord way. With Plucker, you have a new, more efficient, and clearer
option.

## Doc

The arguments of Plucker can be specified in in 3 different ways depending on
the requirements: as a `Symbol`, as a `String`, or as a `Hash`.

When using a symbol, the column with the corresponding name to the symbol is
selected, and the struct field will have that name:

```ruby
post = Post.plucker(:title).last
#

post = Post.joins(:author).plucker(:title, :name).last
#
```

When using the symbol `:*`, it is interpreted as `SELECT *` statement,
selecting all columns from the specified table:

```ruby
post = Post.plucker(:*).last
#
```

When using a string value, the name of the struct field will be generated
using the [`parameterize`](https://www.rubydoc.info/gems/activesupport/String#parameterize-instance_method)
function with an underscore as the separator:

```ruby
post = Post.joins(:comments).plucker('posts.title', 'COUNT(comments.id)').last
#
```

When using the string `table_name.*`, it is interpreted as `SELECT
table_name.*` statement, selecting all columns from the specified table:

```ruby
post = Post.joins(:author).plucker(:*, 'authors.*').last
#
```

When using a Hash, it operates similarly to the String case, except that the
name of the struct field will be the same as the key of the Hash:

```ruby
post = Post.joins(:comments).plucker(:title, comments_count: 'COUNT(comments.id)').last
#
```

Plucker also takes an optional block, which is passed to the struct
definition:

```ruby
posts = Post.plucker(:title) do
def slug
self.title.parameterize
end

def as_json
super.tap do |json|
json['slug'] = self.slug
end
end
end.last
#

post.title
# 'How to make pizza'
post.slug
# 'how-to-make-pizza'
post.as_json
# {"title"=>"How to make pizza", "slug"=>"how-to-make-pizza"}
```

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/pioz/plucker.

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).