Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Selleo/pattern
A collection of lightweight, standardized, rails-oriented patterns.
https://github.com/Selleo/pattern
design-patterns rails-oriented-patterns ruby-gem ruby-on-rails
Last synced: 18 days ago
JSON representation
A collection of lightweight, standardized, rails-oriented patterns.
- Host: GitHub
- URL: https://github.com/Selleo/pattern
- Owner: Selleo
- License: mit
- Created: 2017-04-12T10:58:00.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-06-07T09:12:02.000Z (over 1 year ago)
- Last Synced: 2024-04-14T14:06:40.443Z (7 months ago)
- Topics: design-patterns, rails-oriented-patterns, ruby-gem, ruby-on-rails
- Language: Ruby
- Homepage:
- Size: 105 KB
- Stars: 691
- Watchers: 44
- Forks: 40
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
![](https://github.com/Selleo/pattern/workflows/Ruby/badge.svg)
# Pattern
A collection of lightweight, standardized, rails-oriented patterns used by [RubyOnRails Developers @ Selleo](https://selleo.com/ruby-on-rails)
- [Query - complex querying on active record relation](#query)
- [Service - useful for handling processes involving multiple steps](#service)
- [Collection - when in need to add a method that relates to the collection as whole](#collection)
- [Form - when you need a place for callbacks, want to replace strong parameters or handle virtual/composite resources](#form)
- [Calculation - when you need a place for calculating a simple value (numeric, array, hash) and/or cache it](#calculation)
- [Rule and Ruleset - when you need a place for conditional logic](#rule-and-ruleset)## Installation
```ruby
# Gemfile#...
gem "rails-patterns"
#...
```Then `bundle install`
## Query
### When to use it
One should consider using query objects pattern when in need to perform complex querying on active record relation.
Usually one should avoid using scopes for such purpose.
As a rule of thumb, if scope interacts with more than one column and/or joins in other tables, it should be moved to query object.
Also whenever a chain of scopes is to be used, one should consider using query object too.
Some more information on using query objects can be found in [this article](https://medium.com/@blazejkosmowski/essential-rubyonrails-patterns-part-2-query-objects-4b253f4f4539).### Assumptions and rules
* Query objects are always used by calling class-level `.call` method
* Query objects require `ActiveRecord::Relation` or `ActiveRecord::Base` as constructor argument
* Default relation (see above) can be defined by using `queries` macro
* Query objects have to implement `#query` method that returns `ActiveRecord::Relation`
* Query objects provide access to consecutive keyword arguments using `#options` hash### Other
Because of the fact, that QueryObject implements `.call` method, those can be used to construct scopes if required. ([read more...](http://craftingruby.com/posts/2015/06/29/query-objects-through-scopes.html))
### Examples
#### Declaration
```ruby
class RecentlyActivatedUsersQuery < Patterns::Query
queries Userprivate
def query
relation.active.where(activated_at: date_range)
enddef date_range
options.fetch(:date_range, default_date_range)
enddef default_date_range
Date.yesterday.beginning_of_day..Date.today.end_of_day
end
end
```#### Usage
```ruby
RecentlyActivatedUsersQuery.call
RecentlyActivatedUsersQuery.call(User.without_test_users)
RecentlyActivatedUsersQuery.call(date_range: Date.today.beginning_of_day..Date.today.end_of_day)
RecentlyActivatedUsersQuery.call(User.without_test_users, date_range: Date.today.beginning_of_day..Date.today.end_of_day)class User < ApplicationRecord
scope :recently_activated, RecentlyActivatedUsersQuery
end
```## Service
### When to use it
Service objects are commonly used to mitigate problems with model callbacks that interact with external classes ([read more...](http://samuelmullen.com/2013/05/the-problem-with-rails-callbacks/)).
Service objects are also useful for handling processes involving multiple steps. E.g. a controller that performs more than one operation on its subject (usually a model instance) is a possible candidate for Extract ServiceObject (or Extract FormObject) refactoring. In many cases service object can be used as scaffolding for [replace method with object refactoring](https://sourcemaking.com/refactoring/replace-method-with-method-object). Some more information on using services can be found in [this article](https://medium.com/selleo/essential-rubyonrails-patterns-part-1-service-objects-1af9f9573ca1).### Assumptions and rules
* Service objects are always used by calling class-level `.call` method
* Service objects have to implement `#call` method
* Calling service object's `.call` method executes `#call` and returns service object instance
* A result of `#call` method is accessible through `#result` method
* It is recommended for `#call` method to be the only public method of service object (besides state readers)
* It is recommended to name service object classes after commands (e.g. `ActivateUser` instead of `UserActivation`)### Other
A bit higher level of abstraction is provided by [business_process gem](https://github.com/Selleo/business_process).
### Examples
#### Declaration
```ruby
class ActivateUser < Patterns::Service
def initialize(user)
@user = user
enddef call
user.activate!
NotificationsMailer.user_activation_notification(user).deliver_now
user
endprivate
attr_reader :user
end
```#### Usage
```ruby
user_activation = ActivateUser.call(user)
user_activation.result #