snfoil 0.8.4 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +201 -0
- data/README.md +95 -505
- data/docs/build-context.md +56 -0
- data/docs/create-context.md +109 -0
- data/docs/destroy-context.md +102 -0
- data/docs/index-context.md +70 -0
- data/docs/show-context.md +71 -0
- data/docs/update-context.md +107 -0
- data/lib/{sn_foil → snfoil}/adapters/orms/active_record.rb +14 -0
- data/lib/{sn_foil → snfoil}/adapters/orms/base_adapter.rb +14 -0
- data/lib/snfoil/crud/build_context.rb +49 -0
- data/lib/snfoil/crud/change_context.rb +48 -0
- data/lib/snfoil/crud/context.rb +41 -0
- data/lib/snfoil/crud/create_context.rb +45 -0
- data/lib/snfoil/crud/destroy_context.rb +57 -0
- data/lib/snfoil/crud/index_context.rb +46 -0
- data/lib/snfoil/crud/setup_context.rb +90 -0
- data/lib/snfoil/crud/show_context.rb +52 -0
- data/lib/snfoil/crud/update_context.rb +60 -0
- data/lib/snfoil/version.rb +19 -0
- data/lib/snfoil.rb +69 -0
- data/snfoil.gemspec +47 -0
- metadata +124 -39
- data/Rakefile +0 -4
- data/lib/sn_foil/context.rb +0 -24
- data/lib/sn_foil/contexts/build_context.rb +0 -68
- data/lib/sn_foil/contexts/change_context.rb +0 -101
- data/lib/sn_foil/contexts/create_context.rb +0 -158
- data/lib/sn_foil/contexts/destroy_context.rb +0 -165
- data/lib/sn_foil/contexts/index_context.rb +0 -64
- data/lib/sn_foil/contexts/setup_context.rb +0 -120
- data/lib/sn_foil/contexts/show_context.rb +0 -62
- data/lib/sn_foil/contexts/update_context.rb +0 -169
- data/lib/sn_foil/policy.rb +0 -55
- data/lib/sn_foil/searcher.rb +0 -123
- data/lib/sn_foil/version.rb +0 -5
- data/lib/sn_foil.rb +0 -49
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'active_support/concern'
|
18
|
+
require_relative './change_context'
|
19
|
+
|
20
|
+
module SnFoil
|
21
|
+
module CRUD
|
22
|
+
module CreateContext
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
include BuildContext
|
27
|
+
include ChangeContext
|
28
|
+
|
29
|
+
action :create, with: :create_action
|
30
|
+
|
31
|
+
setup_create { |options| run_interval(:setup, **options) }
|
32
|
+
setup_create { |options| run_interval(:setup_build, **options) }
|
33
|
+
setup_create { |options| run_interval(:setup_change, **options) }
|
34
|
+
before_create { |options| run_interval(:before_change, **options) }
|
35
|
+
after_create_success { |options| run_interval(:after_change_success, **options) }
|
36
|
+
after_create_failure { |options| run_interval(:after_change_failure, **options) }
|
37
|
+
after_create { |options| run_interval(:after_change, **options) }
|
38
|
+
|
39
|
+
def create_action(options)
|
40
|
+
wrap_object(options[:object]).save
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'active_support/concern'
|
18
|
+
require_relative './setup_context'
|
19
|
+
require_relative './change_context'
|
20
|
+
|
21
|
+
module SnFoil
|
22
|
+
module CRUD
|
23
|
+
module DestroyContext
|
24
|
+
extend ActiveSupport::Concern
|
25
|
+
|
26
|
+
included do
|
27
|
+
include SetupContext
|
28
|
+
include ChangeContext
|
29
|
+
|
30
|
+
action :destroy, with: :destroy_action
|
31
|
+
|
32
|
+
setup_destroy { |options| run_interval(:setup, **options) }
|
33
|
+
setup_destroy { |options| run_interval(:setup_change, **options) }
|
34
|
+
before_destroy { |options| run_interval(:before_change, **options) }
|
35
|
+
after_destroy_success { |options| run_interval(:after_change_success, **options) }
|
36
|
+
after_destroy_failure { |options| run_interval(:after_change_failure, **options) }
|
37
|
+
after_destroy { |options| run_interval(:after_change, **options) }
|
38
|
+
|
39
|
+
setup_destroy do |options|
|
40
|
+
raise ArgumentError, 'one of the following keywords is required: id, object' unless options[:id] || options[:object]
|
41
|
+
|
42
|
+
options
|
43
|
+
end
|
44
|
+
|
45
|
+
before_destroy do |options|
|
46
|
+
options[:object] ||= scope.resolve.find(options[:id])
|
47
|
+
|
48
|
+
options
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_action(options)
|
52
|
+
wrap_object(options[:object]).destroy
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'active_support/concern'
|
18
|
+
require_relative './change_context'
|
19
|
+
|
20
|
+
module SnFoil
|
21
|
+
module CRUD
|
22
|
+
module IndexContext
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
include SetupContext
|
27
|
+
|
28
|
+
action :index, with: :index_action
|
29
|
+
|
30
|
+
setup_index { |options| run_interval(:setup, **options) }
|
31
|
+
|
32
|
+
before_index do |options|
|
33
|
+
options[:object] ||= options.fetch(:searcher) { self.class.snfoil_searcher }
|
34
|
+
.new(scope: scope.resolve)
|
35
|
+
.search(options.fetch(:params) { {} })
|
36
|
+
|
37
|
+
options
|
38
|
+
end
|
39
|
+
|
40
|
+
def index_action(**options)
|
41
|
+
options[:object]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'snfoil/context'
|
18
|
+
|
19
|
+
require 'active_support/concern'
|
20
|
+
require 'active_support/core_ext/string/inflections'
|
21
|
+
|
22
|
+
module SnFoil
|
23
|
+
module CRUD
|
24
|
+
module SetupContext
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
|
27
|
+
included do
|
28
|
+
include SnFoil::Context
|
29
|
+
|
30
|
+
interval :setup
|
31
|
+
|
32
|
+
authorize do |options|
|
33
|
+
options.fetch(:policy) { policy }
|
34
|
+
.new(entity, options[:object], **options)
|
35
|
+
.send(options.fetch(:authorize) { "#{options[:action]}?" })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class_methods do
|
40
|
+
attr_reader :snfoil_model, :snfoil_policy, :snfoil_searcher
|
41
|
+
|
42
|
+
def searcher(klass = nil)
|
43
|
+
@snfoil_searcher = klass
|
44
|
+
end
|
45
|
+
|
46
|
+
def model(klass = nil)
|
47
|
+
@snfoil_model = klass
|
48
|
+
end
|
49
|
+
|
50
|
+
def policy(klass = nil)
|
51
|
+
@snfoil_policy = klass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def model
|
56
|
+
self.class.snfoil_model
|
57
|
+
end
|
58
|
+
|
59
|
+
def policy
|
60
|
+
self.class.snfoil_policy
|
61
|
+
end
|
62
|
+
|
63
|
+
def scope(_object_class = nil)
|
64
|
+
"#{policy.name}::Scope".safe_constantize.new(wrap_object(model), entity)
|
65
|
+
end
|
66
|
+
|
67
|
+
def wrap_object(object)
|
68
|
+
return object unless adapter
|
69
|
+
|
70
|
+
adapter.new(object)
|
71
|
+
end
|
72
|
+
|
73
|
+
def unwrap_object(object)
|
74
|
+
return object unless adapter
|
75
|
+
|
76
|
+
adapter?(object) ? object.__getobj__ : object
|
77
|
+
end
|
78
|
+
|
79
|
+
def adapter?(object)
|
80
|
+
return false unless adapter
|
81
|
+
|
82
|
+
object.instance_of? adapter
|
83
|
+
end
|
84
|
+
|
85
|
+
def adapter
|
86
|
+
@adapter ||= SnFoil.adapter
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'active_support/concern'
|
18
|
+
require_relative './setup_context'
|
19
|
+
|
20
|
+
module SnFoil
|
21
|
+
module CRUD
|
22
|
+
module ShowContext
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
include SetupContext
|
27
|
+
|
28
|
+
action :show, with: :show_action
|
29
|
+
|
30
|
+
setup_show do |options|
|
31
|
+
raise ArgumentError, 'one of the following keywords is required: id, object' unless options[:id] || options[:object]
|
32
|
+
|
33
|
+
options
|
34
|
+
end
|
35
|
+
|
36
|
+
setup_show do |options|
|
37
|
+
run_interval(:setup, **options)
|
38
|
+
end
|
39
|
+
|
40
|
+
before_show do |**options|
|
41
|
+
options[:object] ||= scope.resolve.find(options[:id])
|
42
|
+
|
43
|
+
options
|
44
|
+
end
|
45
|
+
|
46
|
+
def show_action(options)
|
47
|
+
options[:object]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'active_support/concern'
|
18
|
+
require_relative './setup_context'
|
19
|
+
require_relative './change_context'
|
20
|
+
|
21
|
+
module SnFoil
|
22
|
+
module CRUD
|
23
|
+
module UpdateContext
|
24
|
+
extend ActiveSupport::Concern
|
25
|
+
|
26
|
+
included do
|
27
|
+
include SetupContext
|
28
|
+
include ChangeContext
|
29
|
+
|
30
|
+
action :update, with: :update_action
|
31
|
+
|
32
|
+
setup_update { |options| run_interval(:setup, **options) }
|
33
|
+
setup_update { |options| run_interval(:setup_change, **options) }
|
34
|
+
before_update { |options| run_interval(:before_change, **options) }
|
35
|
+
after_update_success { |options| run_interval(:after_change_success, **options) }
|
36
|
+
after_update_failure { |options| run_interval(:after_change_failure, **options) }
|
37
|
+
after_update { |options| run_interval(:after_change, **options) }
|
38
|
+
|
39
|
+
setup_update do |options|
|
40
|
+
raise ArgumentError, 'one of the following keywords is required: id, object' unless options[:id] || options[:object]
|
41
|
+
|
42
|
+
options
|
43
|
+
end
|
44
|
+
|
45
|
+
before_update do |options|
|
46
|
+
params = options.fetch(:params) { {} }
|
47
|
+
options[:object] ||= scope.resolve.find(options[:id])
|
48
|
+
|
49
|
+
wrap_object(options[:object]).attributes = params
|
50
|
+
|
51
|
+
options
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_action(options)
|
55
|
+
wrap_object(options[:object]).save
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module SnFoil
|
18
|
+
VERSION = '1.0.1'
|
19
|
+
end
|
data/lib/snfoil.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021 Matthew Howes
|
4
|
+
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
18
|
+
require 'logger'
|
19
|
+
require 'snfoil/context'
|
20
|
+
require 'snfoil/policy'
|
21
|
+
require 'snfoil/searcher'
|
22
|
+
|
23
|
+
require_relative 'snfoil/version'
|
24
|
+
|
25
|
+
require_relative 'snfoil/crud/build_context'
|
26
|
+
require_relative 'snfoil/crud/change_context'
|
27
|
+
require_relative 'snfoil/crud/create_context'
|
28
|
+
require_relative 'snfoil/crud/destroy_context'
|
29
|
+
require_relative 'snfoil/crud/index_context'
|
30
|
+
require_relative 'snfoil/crud/setup_context'
|
31
|
+
require_relative 'snfoil/crud/show_context'
|
32
|
+
require_relative 'snfoil/crud/update_context'
|
33
|
+
require_relative 'snfoil/crud/context'
|
34
|
+
|
35
|
+
require_relative 'snfoil/adapters/orms/base_adapter'
|
36
|
+
require_relative 'snfoil/adapters/orms/active_record'
|
37
|
+
|
38
|
+
module SnFoil
|
39
|
+
class Error < StandardError; end
|
40
|
+
|
41
|
+
mattr_accessor :orm, default: 'active_record'
|
42
|
+
mattr_writer :logger
|
43
|
+
|
44
|
+
class << self
|
45
|
+
def logger
|
46
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
47
|
+
log.progname = name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def adapter
|
52
|
+
return @adapter if @adapter
|
53
|
+
|
54
|
+
@adapter ||= if orm.instance_of?(String) || orm.instance_of?(Symbol)
|
55
|
+
if Object.const_defined?("SnFoil::Adapters::ORMs::#{orm.camelcase}")
|
56
|
+
"SnFoil::Adapters::ORMs::#{orm.camelcase}".constantize
|
57
|
+
else
|
58
|
+
orm.constantize
|
59
|
+
end
|
60
|
+
else
|
61
|
+
orm
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def configure
|
66
|
+
yield self
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/snfoil.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/snfoil/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'snfoil'
|
7
|
+
spec.version = SnFoil::VERSION
|
8
|
+
spec.authors = ['Matthew Howes', 'Danny Murphy', 'Cliff Campbell']
|
9
|
+
spec.email = ['howeszy@gmail.com', 'dmurph24@gmail.com', 'cliffcampbell@hey.com']
|
10
|
+
|
11
|
+
spec.summary = 'A Toolbox of Context Behaviors'
|
12
|
+
spec.description = 'A collection of SnFoil gems and additional helper classes'
|
13
|
+
spec.homepage = 'https://github.com/limited-effort/snfoil'
|
14
|
+
spec.license = 'Apache-2.0'
|
15
|
+
spec.required_ruby_version = '>= 2.6'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/limited-effort/snfoil/blob/main/CHANGELOG.md'
|
20
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
ignore_list = %r{\A(?:test/|spec/|bin/|features/|Rakefile|\.\w)}
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) { `git ls-files -z`.split("\x0").reject { |f| f.match(ignore_list) } }
|
26
|
+
|
27
|
+
spec.bindir = 'exe'
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ['lib']
|
30
|
+
|
31
|
+
spec.add_dependency 'activesupport', '>= 5.2.6'
|
32
|
+
spec.add_dependency 'logger', '~> 1.0'
|
33
|
+
spec.add_dependency 'pundit', '~> 2.0'
|
34
|
+
spec.add_dependency 'snfoil-context', '>= 1.0.1', '< 2.0'
|
35
|
+
spec.add_dependency 'snfoil-policy', '>= 1.0.1', '< 2.0'
|
36
|
+
spec.add_dependency 'snfoil-searcher', '>= 1.0.1', '< 2.0'
|
37
|
+
|
38
|
+
spec.add_development_dependency 'bundle-audit', '~> 0.1.0'
|
39
|
+
spec.add_development_dependency 'dry-struct', '~> 1.0'
|
40
|
+
spec.add_development_dependency 'fasterer', '~> 0.10.0'
|
41
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.9'
|
42
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
43
|
+
spec.add_development_dependency 'rspec', '~> 3.9'
|
44
|
+
spec.add_development_dependency 'rubocop', '1.33'
|
45
|
+
spec.add_development_dependency 'rubocop-performance', '1.14.3'
|
46
|
+
spec.add_development_dependency 'rubocop-rspec', '2.12.1'
|
47
|
+
end
|