auxiliary_rails 0.1.6 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +26 -0
- data/.rubocop.yml +27 -2
- data/.rubocop_todo.yml +5 -15
- data/.yardopts +5 -0
- data/CHANGELOG.md +39 -3
- data/CONTRIBUTING.md +0 -6
- data/Gemfile.lock +116 -92
- data/README.md +213 -6
- data/auxiliary_rails.gemspec +13 -12
- data/bin/rubocop +29 -0
- data/bitbucket-pipelines.yml +35 -0
- data/lib/auxiliary_rails.rb +5 -3
- data/lib/auxiliary_rails/application/command.rb +56 -0
- data/lib/auxiliary_rails/application/error.rb +10 -0
- data/lib/auxiliary_rails/application/form.rb +30 -0
- data/lib/auxiliary_rails/application/query.rb +71 -0
- data/lib/auxiliary_rails/cli.rb +18 -5
- data/lib/auxiliary_rails/concerns/errorable.rb +22 -0
- data/lib/auxiliary_rails/concerns/performable.rb +128 -0
- data/lib/auxiliary_rails/version.rb +1 -1
- data/lib/generators/auxiliary_rails/api_resource_generator.rb +10 -1
- data/lib/generators/auxiliary_rails/command_generator.rb +44 -0
- data/lib/generators/auxiliary_rails/install_commands_generator.rb +6 -1
- data/lib/generators/auxiliary_rails/install_generator.rb +0 -1
- data/lib/generators/auxiliary_rails/templates/apis/api_entity_template.rb.erb +2 -1
- data/lib/generators/auxiliary_rails/templates/apis/api_resource_spec_template.rb.erb +36 -9
- data/lib/generators/auxiliary_rails/templates/apis/api_resource_template.rb.erb +12 -6
- data/lib/generators/auxiliary_rails/templates/application_error_template.rb +1 -1
- data/lib/generators/auxiliary_rails/templates/commands/application_command_template.rb +2 -0
- data/lib/generators/auxiliary_rails/templates/commands/command_spec_template.rb +11 -0
- data/lib/generators/auxiliary_rails/templates/commands/command_template.rb +6 -0
- data/lib/generators/auxiliary_rails/templates/commands/commands.en_template.yml +5 -0
- data/templates/rails/elementary.rb +40 -10
- metadata +81 -24
- data/lib/auxiliary_rails/abstract_command.rb +0 -96
- data/lib/auxiliary_rails/abstract_error.rb +0 -7
- data/lib/generators/auxiliary_rails/install_rubocop_generator.rb +0 -29
- data/lib/generators/auxiliary_rails/templates/application_command_template.rb +0 -2
- data/lib/generators/auxiliary_rails/templates/rubocop/rubocop_auxiliary_rails_template.yml +0 -51
- data/lib/generators/auxiliary_rails/templates/rubocop/rubocop_template.yml +0 -7
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Collection of classes, configs, scripts, generators for Ruby on Rails helping yo
|
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
11
|
-
Add one of these lines to your application's Gemfile
|
11
|
+
Add one of these lines to your application's `Gemfile`:
|
12
12
|
|
13
13
|
```ruby
|
14
14
|
# version released to RubyGems
|
@@ -29,31 +29,238 @@ Or install it yourself as:
|
|
29
29
|
|
30
30
|
## Usage
|
31
31
|
|
32
|
+
- [API documentation](https://www.rubydoc.info/gems/auxiliary_rails)
|
33
|
+
|
32
34
|
### Rails Application Templates
|
33
35
|
|
34
36
|
Install gem into the system (e.g. using `gem install auxiliary_rails`) then:
|
35
37
|
|
36
38
|
```sh
|
37
39
|
auxiliary_rails new APP_PATH
|
40
|
+
# or add `--develop` option to pull the most recent template from repository
|
41
|
+
auxiliary_rails new APP_PATH --develop
|
38
42
|
```
|
39
43
|
|
40
44
|
Or use `rails new` command specifying `--template` argument:
|
41
45
|
|
42
46
|
```sh
|
43
|
-
rails new APP_PATH --
|
47
|
+
rails new APP_PATH --database=postgresql --template=https://raw.githubusercontent.com/ergoserv/auxiliary_rails/develop/templates/rails/elementary.rb --skip-action-cable --skip-coffee --skip-test
|
44
48
|
```
|
45
49
|
|
46
50
|
### Generators
|
47
51
|
|
48
52
|
```sh
|
49
|
-
#
|
53
|
+
# Install everything at once
|
50
54
|
rails generate auxiliary_rails:install
|
51
55
|
|
52
|
-
#
|
56
|
+
# Install one by one
|
53
57
|
rails generate auxiliary_rails:install_commands
|
54
58
|
rails generate auxiliary_rails:install_errors
|
55
|
-
|
56
|
-
|
59
|
+
|
60
|
+
# API resource generator
|
61
|
+
rails generate auxiliary_rails:api_resource
|
62
|
+
|
63
|
+
# Command generator
|
64
|
+
rails generate auxiliary_rails:command
|
65
|
+
```
|
66
|
+
|
67
|
+
### Command Objects
|
68
|
+
|
69
|
+
Variation of implementation of [Command pattern](https://en.wikipedia.org/wiki/Command_pattern).
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# app/commands/application_command.rb
|
73
|
+
class ApplicationCommand < AuxiliaryRails::Application::Command
|
74
|
+
end
|
75
|
+
|
76
|
+
# app/commands/register_user_command.rb
|
77
|
+
class RegisterUserCommand < ApplicationCommand
|
78
|
+
# Define command arguments
|
79
|
+
# using `param` or `option` methods provided by dry-initializer
|
80
|
+
# https://dry-rb.org/gems/dry-initializer/3.0/
|
81
|
+
param :email
|
82
|
+
param :password
|
83
|
+
|
84
|
+
# Define the results of the command
|
85
|
+
# using `attr_reader` and set it as a regular instance var inside the command
|
86
|
+
attr_reader :user
|
87
|
+
|
88
|
+
# Regular Active Model Validations can be used to validate params
|
89
|
+
# https://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
90
|
+
# Use #valid?, #invalid?, #validate! methods to engage validations
|
91
|
+
validates :password, length: { in: 8..32 }
|
92
|
+
|
93
|
+
# Define the only public method `#perform`
|
94
|
+
# where command's flow is defined
|
95
|
+
def perform
|
96
|
+
# Use `return failure!` to exit from the command with failure
|
97
|
+
return failure! if registration_disabled?
|
98
|
+
|
99
|
+
# Method `#transaction` is a shortcut for `ActiveRecord::Base.transaction`
|
100
|
+
transaction do
|
101
|
+
# Keep the `#perform` method short and clean, put all the steps, actions
|
102
|
+
# and business logic into meaningful and self-explanatory methods
|
103
|
+
create_user
|
104
|
+
|
105
|
+
# Use `error!` method to interrupt the flow raising an error
|
106
|
+
error! unless @user.persistent?
|
107
|
+
|
108
|
+
send_notification
|
109
|
+
# ...
|
110
|
+
end
|
111
|
+
|
112
|
+
# Always end the `#perform` method with `success!`
|
113
|
+
# this will set the proper status and allow to chain command methods.
|
114
|
+
success!
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def create_user
|
120
|
+
@user = User.create(email: email, password: password)
|
121
|
+
end
|
122
|
+
|
123
|
+
def send_notification
|
124
|
+
# ...
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
### usage ###
|
129
|
+
|
130
|
+
class RegistrationsController
|
131
|
+
def register
|
132
|
+
cmd = RegisterUserCommand.call(params[:email], params[:password])
|
133
|
+
|
134
|
+
if cmd.success?
|
135
|
+
redirect_to user_path(cmd.user) and return
|
136
|
+
else
|
137
|
+
@errors = cmd.errors
|
138
|
+
end
|
139
|
+
|
140
|
+
### OR ###
|
141
|
+
|
142
|
+
RegisterUserCommand.call(params[:email], params[:password])
|
143
|
+
.on(:success) do
|
144
|
+
redirect_to dashboard_path and return
|
145
|
+
end
|
146
|
+
.on(:failure) do |cmd|
|
147
|
+
@errors = cmd.errors
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
### Form Objects
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# app/forms/application_form.rb
|
157
|
+
class ApplicationForm < AuxiliaryRails::Application::Form
|
158
|
+
end
|
159
|
+
|
160
|
+
# app/forms/company_registration_form.rb
|
161
|
+
class CompanyRegistrationForm < ApplicationForm
|
162
|
+
# Define form attributes
|
163
|
+
attribute :company_name, :string
|
164
|
+
attribute :email, :string
|
165
|
+
|
166
|
+
# Define form submission results
|
167
|
+
attr_reader :company
|
168
|
+
|
169
|
+
# Regular Active Model Validations can be used to validate attributes
|
170
|
+
# https://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
171
|
+
validates :company_name, presence: true
|
172
|
+
validates :email, email: true
|
173
|
+
|
174
|
+
def perform
|
175
|
+
# Perform business logic here
|
176
|
+
|
177
|
+
# Use `attr_reader` to expose the submission results.
|
178
|
+
@company = create_company
|
179
|
+
# Return `failure!` to indicate failure and stop execution
|
180
|
+
return failure! if @company.invalid?
|
181
|
+
|
182
|
+
send_notification if email.present?
|
183
|
+
|
184
|
+
# Always end with `success!` method call to indicate success
|
185
|
+
success!
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def create_comany
|
191
|
+
Company.create(name: company_name)
|
192
|
+
end
|
193
|
+
|
194
|
+
def send_notification
|
195
|
+
# mail to: email
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
### Usage ###
|
200
|
+
|
201
|
+
form = CompanyRegistrationForm.call(params[:company])
|
202
|
+
if form.success?
|
203
|
+
redirect_to company_path(form.company) and return
|
204
|
+
else
|
205
|
+
@errors = form.errors
|
206
|
+
end
|
207
|
+
```
|
208
|
+
|
209
|
+
### Query Objects
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
# app/queries/application_query.rb
|
213
|
+
class ApplicationQuery < AuxiliaryRails::Application::Query
|
214
|
+
end
|
215
|
+
|
216
|
+
# app/queries/authors_query.rb
|
217
|
+
class AuthorsQuery < ApplicationQuery
|
218
|
+
default_relation Author.all
|
219
|
+
|
220
|
+
option :name_like, optional: true
|
221
|
+
option :with_books, optional: true
|
222
|
+
|
223
|
+
def perform
|
224
|
+
if recent == true
|
225
|
+
# equivalent to `@query = @query.order(:created_at)`:
|
226
|
+
query order(:created_at)
|
227
|
+
end
|
228
|
+
|
229
|
+
if name_like.present?
|
230
|
+
query with_name_like(name_like)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def with_name_like(value)
|
237
|
+
where('authors.name LIKE ?', "%#{value}%")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# app/queries/authors_with_books_query.rb
|
242
|
+
class AuthorsWithBooksQuery < AuthorsQuery
|
243
|
+
option :min_book_count, default: { 3 }
|
244
|
+
|
245
|
+
def perform
|
246
|
+
query joins(:books)
|
247
|
+
.group(:author_id)
|
248
|
+
.having('COUNT(books.id) > ?', min_book_count)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
### Usage ###
|
253
|
+
|
254
|
+
# it is possible to wrap query object in a scope and use as a regular scope
|
255
|
+
# app/models/inmate.rb
|
256
|
+
class Author < ApplicationRecord
|
257
|
+
scope :name_like, ->(value) { AuthorsQuery.call(name_like: value) }
|
258
|
+
end
|
259
|
+
|
260
|
+
authors = Author.name_like('Arthur')
|
261
|
+
|
262
|
+
# or call query directly
|
263
|
+
authors = AuthorsWithBooksQuery.call(min_book_count: 10)
|
57
264
|
```
|
58
265
|
|
59
266
|
### View Helpers
|
data/auxiliary_rails.gemspec
CHANGED
@@ -17,15 +17,13 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.homepage = 'https://github.com/ergoserv/auxiliary_rails'
|
18
18
|
spec.license = 'MIT'
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
raise 'RubyGems 2.0 or newer is required'
|
26
|
-
end
|
20
|
+
raise 'RubyGems 2.0 or newer is required' unless spec.respond_to?(:metadata)
|
21
|
+
|
22
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
23
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
24
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/releases"
|
27
25
|
|
28
|
-
spec.files
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
29
27
|
`git ls-files -z`.split("\x0").reject do |f|
|
30
28
|
f.match(%r{^(test|spec|features)/})
|
31
29
|
end
|
@@ -35,12 +33,15 @@ Gem::Specification.new do |spec|
|
|
35
33
|
|
36
34
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
37
35
|
spec.add_development_dependency 'pry'
|
38
|
-
spec.add_development_dependency '
|
39
|
-
spec.add_development_dependency '
|
40
|
-
spec.add_development_dependency '
|
41
|
-
spec.add_development_dependency 'rubocop'
|
36
|
+
spec.add_development_dependency 'rake'
|
37
|
+
spec.add_development_dependency 'rspec', '~> 3.8'
|
38
|
+
spec.add_development_dependency 'rubocop', '~> 0.80'
|
42
39
|
spec.add_development_dependency 'rubocop-performance'
|
43
40
|
spec.add_development_dependency 'rubocop-rspec'
|
44
41
|
|
42
|
+
spec.add_runtime_dependency 'dry-core'
|
43
|
+
spec.add_runtime_dependency 'dry-initializer'
|
44
|
+
spec.add_runtime_dependency 'dry-initializer-rails'
|
45
|
+
spec.add_runtime_dependency 'rails', '>= 5.2', '< 7'
|
45
46
|
spec.add_runtime_dependency 'thor'
|
46
47
|
end
|
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rubocop", "rubocop")
|
@@ -0,0 +1,35 @@
|
|
1
|
+
image: ruby:2.6.5
|
2
|
+
|
3
|
+
definitions:
|
4
|
+
caches:
|
5
|
+
bundler: vendor/bundle
|
6
|
+
|
7
|
+
pipelines:
|
8
|
+
default:
|
9
|
+
- step:
|
10
|
+
name: Build
|
11
|
+
caches:
|
12
|
+
- bundler
|
13
|
+
script:
|
14
|
+
- gem install bundler
|
15
|
+
- bundle config set path vendor/bundle
|
16
|
+
- bundle install
|
17
|
+
- parallel:
|
18
|
+
- step:
|
19
|
+
name: RSpec
|
20
|
+
caches:
|
21
|
+
- bundler
|
22
|
+
script:
|
23
|
+
- gem install bundler
|
24
|
+
- bundle config set path vendor/bundle
|
25
|
+
- bundle install
|
26
|
+
- bundle exec rspec
|
27
|
+
- step:
|
28
|
+
name: RuboCop
|
29
|
+
caches:
|
30
|
+
- bundler
|
31
|
+
script:
|
32
|
+
- gem install bundler
|
33
|
+
- bundle config set path vendor/bundle
|
34
|
+
- bundle install
|
35
|
+
- bundle exec rubocop
|
data/lib/auxiliary_rails.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
require 'auxiliary_rails/
|
2
|
-
require 'auxiliary_rails/
|
3
|
-
require 'auxiliary_rails/
|
1
|
+
require 'auxiliary_rails/application/command'
|
2
|
+
require 'auxiliary_rails/application/form'
|
3
|
+
require 'auxiliary_rails/application/error'
|
4
|
+
require 'auxiliary_rails/application/query'
|
5
|
+
require 'auxiliary_rails/railtie'
|
4
6
|
require 'auxiliary_rails/version'
|
5
7
|
|
6
8
|
module AuxiliaryRails
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'auxiliary_rails/concerns/performable'
|
3
|
+
require 'dry-initializer-rails'
|
4
|
+
|
5
|
+
module AuxiliaryRails
|
6
|
+
module Application
|
7
|
+
# @abstract
|
8
|
+
class Command
|
9
|
+
extend Dry::Initializer
|
10
|
+
include AuxiliaryRails::Concerns::Performable
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @!method param(name, options = {})
|
14
|
+
# Defines param using <tt>Dry::Initializer</tt> format.
|
15
|
+
#
|
16
|
+
# @see Dry::Initializer
|
17
|
+
# @see https://dry-rb.org/gems/dry-initializer/3.0/params-and-options/
|
18
|
+
#
|
19
|
+
# @param name [Symbol]
|
20
|
+
# @param options [Hash]
|
21
|
+
|
22
|
+
# @!method option(name, options = {})
|
23
|
+
# Defines option using <tt>Dry::Initializer</tt> format.
|
24
|
+
|
25
|
+
# @see Dry::Initializer
|
26
|
+
# @see https://dry-rb.org/gems/dry-initializer/3.0/params-and-options/
|
27
|
+
#
|
28
|
+
# @param name [Symbol]
|
29
|
+
# @param options [Hash]
|
30
|
+
|
31
|
+
# Initializes command with <tt>args</tt> and runs <tt>#call</tt> method.
|
32
|
+
#
|
33
|
+
# @return [self]
|
34
|
+
def call(*args)
|
35
|
+
new(*args).call
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defines `scope` for <tt>ActiveModel::Translation</tt>
|
39
|
+
#
|
40
|
+
# @return [Symbol]
|
41
|
+
def i18n_scope
|
42
|
+
:commands
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# Shortcut reader for attributes defined by <tt>Dry::Initializer</tt>
|
49
|
+
#
|
50
|
+
# @return [Hash]
|
51
|
+
def arguments
|
52
|
+
self.class.dry_initializer.attributes(self)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module AuxiliaryRails
|
4
|
+
module Application
|
5
|
+
# @abstract
|
6
|
+
class Form
|
7
|
+
include ActiveModel::Model
|
8
|
+
include ActiveModel::Attributes
|
9
|
+
include ActiveModel::AttributeAssignment
|
10
|
+
include AuxiliaryRails::Concerns::Performable
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Defines `scope` for <tt>ActiveModel::Translation</tt>
|
14
|
+
#
|
15
|
+
# @return [Symbol]
|
16
|
+
def i18n_scope
|
17
|
+
:forms
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Indicates object persistence.
|
22
|
+
#
|
23
|
+
# In case of form as performable object that means that form
|
24
|
+
# was executed with success.
|
25
|
+
def persisted?
|
26
|
+
performable_status == true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|