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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +26 -0
  3. data/.rubocop.yml +27 -2
  4. data/.rubocop_todo.yml +5 -15
  5. data/.yardopts +5 -0
  6. data/CHANGELOG.md +39 -3
  7. data/CONTRIBUTING.md +0 -6
  8. data/Gemfile.lock +116 -92
  9. data/README.md +213 -6
  10. data/auxiliary_rails.gemspec +13 -12
  11. data/bin/rubocop +29 -0
  12. data/bitbucket-pipelines.yml +35 -0
  13. data/lib/auxiliary_rails.rb +5 -3
  14. data/lib/auxiliary_rails/application/command.rb +56 -0
  15. data/lib/auxiliary_rails/application/error.rb +10 -0
  16. data/lib/auxiliary_rails/application/form.rb +30 -0
  17. data/lib/auxiliary_rails/application/query.rb +71 -0
  18. data/lib/auxiliary_rails/cli.rb +18 -5
  19. data/lib/auxiliary_rails/concerns/errorable.rb +22 -0
  20. data/lib/auxiliary_rails/concerns/performable.rb +128 -0
  21. data/lib/auxiliary_rails/version.rb +1 -1
  22. data/lib/generators/auxiliary_rails/api_resource_generator.rb +10 -1
  23. data/lib/generators/auxiliary_rails/command_generator.rb +44 -0
  24. data/lib/generators/auxiliary_rails/install_commands_generator.rb +6 -1
  25. data/lib/generators/auxiliary_rails/install_generator.rb +0 -1
  26. data/lib/generators/auxiliary_rails/templates/apis/api_entity_template.rb.erb +2 -1
  27. data/lib/generators/auxiliary_rails/templates/apis/api_resource_spec_template.rb.erb +36 -9
  28. data/lib/generators/auxiliary_rails/templates/apis/api_resource_template.rb.erb +12 -6
  29. data/lib/generators/auxiliary_rails/templates/application_error_template.rb +1 -1
  30. data/lib/generators/auxiliary_rails/templates/commands/application_command_template.rb +2 -0
  31. data/lib/generators/auxiliary_rails/templates/commands/command_spec_template.rb +11 -0
  32. data/lib/generators/auxiliary_rails/templates/commands/command_template.rb +6 -0
  33. data/lib/generators/auxiliary_rails/templates/commands/commands.en_template.yml +5 -0
  34. data/templates/rails/elementary.rb +40 -10
  35. metadata +81 -24
  36. data/lib/auxiliary_rails/abstract_command.rb +0 -96
  37. data/lib/auxiliary_rails/abstract_error.rb +0 -7
  38. data/lib/generators/auxiliary_rails/install_rubocop_generator.rb +0 -29
  39. data/lib/generators/auxiliary_rails/templates/application_command_template.rb +0 -2
  40. data/lib/generators/auxiliary_rails/templates/rubocop/rubocop_auxiliary_rails_template.yml +0 -51
  41. 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 --skip-action-cable --skip-coffee --skip-test --database=postgresql --template=https://raw.githubusercontent.com/ergoserv/auxiliary_rails/develop/templates/rails/elementary.rb
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
- # install everything
53
+ # Install everything at once
50
54
  rails generate auxiliary_rails:install
51
55
 
52
- # install one by one
56
+ # Install one by one
53
57
  rails generate auxiliary_rails:install_commands
54
58
  rails generate auxiliary_rails:install_errors
55
- rails generate auxiliary_rails:install_rubocop
56
- rails generate auxiliary_rails:install_rubocop --no-specify-gems
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
@@ -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
- if spec.respond_to?(:metadata)
21
- spec.metadata['homepage_uri'] = 'https://github.com/ergoserv/auxiliary_rails'
22
- spec.metadata['source_code_uri'] = 'https://github.com/ergoserv/auxiliary_rails'
23
- spec.metadata['changelog_uri'] = 'https://github.com/ergoserv/auxiliary_rails/blob/master/CHANGELOG.md'
24
- else
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 = Dir.chdir(File.expand_path(__dir__)) do
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 'rails', '~> 5.2'
39
- spec.add_development_dependency 'rake', '~> 10.0'
40
- spec.add_development_dependency 'rspec', '~> 3.0'
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
@@ -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
@@ -1,6 +1,8 @@
1
- require 'auxiliary_rails/abstract_error'
2
- require 'auxiliary_rails/abstract_command'
3
- require 'auxiliary_rails/railtie' if defined?(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,10 @@
1
+ module AuxiliaryRails
2
+ module Application
3
+ class Error < StandardError
4
+ # @return [self] Creates new error object
5
+ def self.wrap(error)
6
+ new(error.message)
7
+ end
8
+ end
9
+ end
10
+ 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