https://github.com/MoskitoHero/barley
Barley is a fast and efficient ActiveModel serializer
https://github.com/MoskitoHero/barley
activerecord cache rails ruby serializer type-check
Last synced: 12 months ago
JSON representation
Barley is a fast and efficient ActiveModel serializer
- Host: GitHub
- URL: https://github.com/MoskitoHero/barley
- Owner: MoskitoHero
- License: mit
- Created: 2023-10-09T04:53:49.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-09-25T03:43:52.000Z (almost 2 years ago)
- Last Synced: 2024-11-19T15:47:51.593Z (over 1 year ago)
- Topics: activerecord, cache, rails, ruby, serializer, type-check
- Language: Ruby
- Homepage:
- Size: 1.05 MB
- Stars: 80
- Watchers: 1
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: MIT-LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
Awesome Lists containing this project
README


[](https://badge.fury.io/rb/barley)

Barley is a fast and efficient ActiveModel serializer.
Cerealize your ActiveModel objects into flat hashes with a clear, yet versatile DSL, and caching and type-checking baked in. Our daily bread is to make your API faster.
You don't believe us? Check out the [benchmarks](https://github.com/MoskitoHero/barley/tree/main/benchmarks#readme). 😎
## API documentation
[Check out the API documentation here](https://rubydoc.info/github/MoskitoHero/barley/main).
## Usage
Add the `Barley::Serializable` module to your ActiveModel object.
```ruby
# /app/models/user.rb
class User < ApplicationRecord
include Barley::Serializable
end
```
Then define your attributes and associations in a serializer class.
```ruby
# /app/serializers/user_serializer.rb
class UserSerializer < Barley::Serializer
attributes id: Types::Strict::Integer, :name
attribute :email
attribute :value, type: Types::Coercible::Integer
many :posts
many :posts, key_name: :featured, scope: :featured
many :posts, key_name: :popular, scope: -> { where("views > 10_000").limit(3) }
many :posts, key_name: :in_current_language, scope: -> (context) { where(language: context.language) }
one :group, serializer: CustomGroupSerializer
many :related_users, key: :friends, cache: true
one :profile, cache: { expires_in: 1.day } do
attributes :avatar, :social_url
attribute :badges do
object.badges.map(&:display_name)
end
end
end
```
Then just use the `as_json` method on your model.
```ruby
user = User.find(1)
user.as_json(only: [:id, :name, posts: [:id, :title]])
```
## Installation
Add this line to your application's Gemfile:
```ruby
gem "barley"
```
And then execute:
```bash
$ bundle
```
Or install it yourself as:
```bash
$ gem install barley
```
## Defining the serializer
Barley uses the model name to find the serializer class. For example, if you have a `User` model, Barley will look for a `UserSerializer` class.
You can also define the serializer class with the `serializer` macro.
```ruby
# /app/models/user.rb
class User < ApplicationRecord
include Barley::Serializable
serializer UserSerializer
end
```
## DSL
### Attributes
You can define attributes with the `attributes` macro.
```ruby
attributes :id, :name, :email, :created_at, :updated_at
```
You can also define attributes one by one, or a mix of both.
```ruby
attributes :id, :name, :email
attribute :created_at
attribute :updated_at
```
You can also define a custom attribute with a block. You will have a `object` variable available in the block. It is the object you are serializing.
```ruby
attribute :full_name do
"#{object.first_name} #{object.last_name}"
end
```
You can also set a custom key name for the attribute with the `key_name` option.
```ruby
attribute :updated_at, key: :last_change
```
### Associations
#### One-to-one
You can define a one-to-one association with the `one` macro.
```ruby
one :group
```
##### Custom serializer and caching
You can define a custom serializer for the association with the `serializer` option, and / or caching options with the `cache` option.
```ruby
one :group, serializer: CustomGroupSerializer, cache: { expires_in: 1.hour }
```
You can of course define serializers with inner classes for simple needs.
```ruby
class UserSerializer < Barley::Serializer
attributes :id, :name, :email, :created_at, :updated_at
one :group, serializer: LocalGroupSerializer
class LocalGroupSerializer < Barley::Serializer
attributes :id, :name
end
end
```
##### Key name
You can also pass a key name for the association with the `key_name` option.
```ruby
one :group, key_name: :team
```
#### One-to-many
You can define a one-to-many association with the `many` macro.
```ruby
many :posts
```
##### Custom serializer and caching
You can define a custom serializer for the association with the `serializer` option, and / or caching options with the `cache` option.
```ruby
many :posts, serializer: CustomPostSerializer, cache: { expires_in: 1.hour }
```
##### Scope
You can pass a scope to the association with the `scope` option. It can either be a symbol referencing a named scope on your associated model, or a lambda.
```ruby
many :posts, scope: :published # given you have a scope named `published` on your Post model
```
```ruby
many :posts, scope: -> { where(published: true).limit(4) }
```
You can also pass a context to the lambda. See the [context section](#context) for more details.
```ruby
many :posts, scope: -> (context) { where(language: context.language) }
```
##### Key name
You can also pass a key name for the association with the `key_name` option.
```ruby
many :posts, key_name: :articles
```
### Associations with blocks
Feel like using a block to define your associations? You can do that too.
```ruby
one :group do
attributes :id, :name
end
```
```ruby
many :posts do
attributes :id, :title, :body
one :author do
attributes :name, :email
end
end
```
Of course, all the options available for the `one` and `many` macros are also available for the block syntax.
```ruby
many :posts, key_name: :featured do
attributes :id, :title, :body
end
```
## Context
You can pass a context to the serializer with the `with_context` method.
```ruby
serializer = PostSerializer.new(Post.last).with_context(current_user: current_user)
```
This context will be available in the serializer with the `context` method. It is also available in nested serializers.
```ruby
class PostSerializer < Barley::Serializer
attributes :id, :title, :body
attribute :is_owner do
object.user == context.current_user
end
many :comments do
many :likes do
attribute :is_owner do
object.user == context.current_user # context is here too!
end
end
end
end
```
The context is also available in the scope of the lambda passed to the `scope` option of the `many` macro. See the [scope section](#scope) for more details.
```ruby
many :posts, scope: -> (context) { where(language: context.language) }
```
### Using a custom context object
Barley generates a Struct from the context hash you pass to the with_context method. But you can also pass a custom context object directly in the initializer instead.
```ruby
my_context = Struct.new(:current_user).new(current_user)
serializer = PostSerializer.new(Post.last, context: my_context)
```
## Generators
You have two generators available. One to generate the serializer class:
```shell
rails generate barley:serializer User
# or
rails generate barley:serializer User --name=CustomUserSerializer
```
And one to generate both the serializer class and add the module to the model:
```shell
rails generate barley:serializable User
# or
rails generate barley:serializable User --name=CustomUserSerializer
```
## Serialization options
You can pass a hash of options to the `as_json` method.
```ruby
user = User.find(1)
user.as_json(serializer: CustomUserSerializer, cache: { expires_in: 1.hour })
```
Beware, this gem overrides the `as_json` method on your model. Calling `as_json` with `include`, `only`, or `except` options will not work as expected.
Why? We believe it defeats the purpose of this gem. If you want to customize the serialization of your model, you should use a serializer class.
## Caching
Barley supports caching out of the box. Just pass `cache: true` to the `serializer` macro.
```ruby
# /app/models/user.rb
# ...
serializer UserSerializer, cache: true
```
Or you can pass a hash of options to the `serializer` macro.
```ruby
# /app/models/user.rb
# ...
serializer UserSerializer, cache: { expires_in: 1.hour }
```
### Caching options
Barley uses the MemoryStore by default. You can change the cache store with the `cache_store` option in an initializer.
```ruby
# /config/initializers/barley.rb
Barley.configure do |config|
config.cache_store = ActiveSupport::Cache::RedisCacheStore.new
end
```
## Type checking
Barley can check the type of the object you are serializing with the [dry-types](https://dry-rb.org/gems/dry-types/main/) gem.
It will raise an error if the object is not of the expected type, or coerce it to the correct type and perform constraints checks.
```ruby
module Types
include Dry.Types()
end
class UserSerializer < Barley::Serializer
attributes id: Types::Strict::Integer, name: Types::Strict::String, email: Types::Strict::String.constrained(format: URI::MailTo::EMAIL_REGEXP)
attribute :role, type: Types::Coercible::String do
object.role.integer_or_string_coercible_value
end
end
```
Check out [dry-types](https://dry-rb.org/gems/dry-types/main/) for all options and available types.
## Breakfast mode 🤡 (coming soon)
You will soon be able to replace all occurrences of `Serializer` with `Cerealizer` in your codebase. Just for fun. And for free.
```ruby
# /app/models/user.rb
class User < ApplicationRecord
include Barley::Cerealizable
cerealizer UserCerealizer
end
# app/cerealizers/user_cerealizer.rb
class UserCerealizer < Barley::Cerealizer
attributes :id, :name, :email, :created_at, :updated_at
many :posts
one :group
end
```
```shell
rails generate barley:cerealizer User
# etc.
```
Ah ah ah. This is so funny.
*Note: we are thinking about adding a `Surrealizer` class for the most advanced users. Stay tuned.*
## JSON:API
Barley does not serialize to the JSON:API standard. We prefer to keep it simple and fast.
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
The logo is made from an asset from [onlinewebfonts.com](https://www.onlinewebfonts.com/icon), licensed by CC BY 4.0.
## Contributing
You can contribute in several ways: reporting bugs, suggesting features, or contributing code. See [our contributing guidelines](CONTRIBUTING.md)
Make sure you adhere to [our code of conduct](CODE_OF_CONDUCT.md). We aim to keep this project open and inclusive.
## Security
Please refer to our [security guidelines](SECURITY.md)