spicerack 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cb81265e19c0b0d16717461b0d4cd4c5ca97e0f5c8ae6c0ab66ea47d1c1b980
4
- data.tar.gz: 70616ba354fe24be34d0ef6cf786bbd4a090d1411ee183d418c518b108a2e50b
3
+ metadata.gz: e95ea2fbed7c71289fc97dde75a0eb541cbf6499c4fa53d9ce9e42a7cfaf0592
4
+ data.tar.gz: 47f05cd6e75645c2a5b4e806c62f9fbda934544ccb9b13ff46a86dd4e368938c
5
5
  SHA512:
6
- metadata.gz: 199e2b8b16baa887e5cbf2fef2360d8e812b89db3f8737cd0b4dd9def84883db185b062606bd0f9757fd5c69dc00d343949f62648467daeb877a5f6cb6ed9057
7
- data.tar.gz: 17a2abba67e0743e71f2df0820c6c59b7bab8ddb6ea52b06ae386bd2da1d2296c10faa61e50542dbb31f9293eeb4fa22ab5e15923c59e1302993cdd818a83ed7
6
+ metadata.gz: e79d5dee503f0e59125a61b687fd10b8b623591110221619f928a4d7e610c133cd137e3ce08f167e6e5eaaaa91e1e877aac00ae0388cf86abd60d60ee72531f0
7
+ data.tar.gz: 5f57ab2a707247497a04ce6c99b55a5d3c986ffeb434fc22afe10b7be02661f1d98634817856fab640678331e6c0b38cfb65e8873f45748f55adfeb58add3df1
data/README.md CHANGED
@@ -35,10 +35,7 @@ Or install it yourself as:
35
35
  ## Included Gems
36
36
 
37
37
  * [AroundTheWorld](around_the_world/README.md) allows you to easily wrap methods with custom logic on any class.
38
- * [Ascriptor](ascriptor/README.md) facilitates attribute definition on classes to easily create service object.
39
- * [Instructor](instructor/README.md) allows you to clearly require and validate input with a base class for service object.
40
38
  * [RedisHash](redis_hash/README.md) provides a class that matches the Hash api by wrapping Redis.
41
- * [RootObject](root_object/README.md) is a generic baseplate object full of convenient utility methods.
42
39
  * [RSpice](rspice/README.md) is an `RSpec` utility gem of custom matchers, shared contexts and examples.
43
40
  * [ShortCircuIt](short_circu_it/README.md) is an intelligent and feature rich memoization gem.
44
41
  * [Spicerack::Styleguide](spicerack-styleguide/README.md) is [Freshly](https://www.freshly.com/)'s Rubocop Styleguide for Rails and RSpec.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "objects/defaults"
4
+ require_relative "objects/attributes"
5
+
6
+ module Spicerack
7
+ class AttributeObject < Spicerack::RootObject
8
+ include Spicerack::Objects::Defaults
9
+ include Spicerack::Objects::Attributes
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spicerack
4
+ class InputModel < Spicerack::InputObject
5
+ extend ActiveModel::Naming
6
+ extend ActiveModel::Translation
7
+
8
+ include ActiveModel::Conversion
9
+ include ActiveModel::Validations
10
+ include ActiveModel::Validations::Callbacks
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "objects/arguments"
4
+ require_relative "objects/options"
5
+
6
+ module Spicerack
7
+ class InputObject < Spicerack::AttributeObject
8
+ define_callbacks :initialize
9
+
10
+ include Spicerack::Objects::Arguments
11
+ include Spicerack::Objects::Options
12
+
13
+ def initialize(**input)
14
+ @input = input
15
+ run_callbacks(:initialize) do
16
+ input.each { |key, value| __send__("#{key}=".to_sym, value) }
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :input
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Arguments describe input that is required (which can be nil, unless otherwise specified).
4
+ module Spicerack
5
+ module Objects
6
+ module Arguments
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :_arguments, instance_writer: false, default: {}
11
+ set_callback :initialize, :after do
12
+ missing_arguments = _arguments.reject do |argument, options|
13
+ options[:allow_nil] ? input.key?(argument) : !input[argument].nil?
14
+ end
15
+
16
+ missing = missing_arguments.keys
17
+
18
+ raise ArgumentError, "Missing #{"argument".pluralize(missing.length)}: #{missing.join(", ")}" if missing.any?
19
+ end
20
+ end
21
+
22
+ class_methods do
23
+ def inherited(base)
24
+ dup = _arguments.dup
25
+ base._arguments = dup.each { |k, v| dup[k] = v.dup }
26
+ super
27
+ end
28
+
29
+ private
30
+
31
+ def argument(argument, allow_nil: true)
32
+ _arguments[argument] = { allow_nil: allow_nil }
33
+ define_attribute argument
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Attributes are structured data within an object.
4
+ module Spicerack
5
+ module Objects
6
+ module Attributes
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :_attributes, instance_writer: false, default: []
11
+ end
12
+
13
+ class_methods do
14
+ def inherited(base)
15
+ base._attributes = _attributes.dup
16
+ super
17
+ end
18
+
19
+ private
20
+
21
+ def define_attribute(attribute)
22
+ _attributes << attribute
23
+ attr_accessor attribute
24
+ end
25
+ alias_method :attribute, :define_attribute
26
+ end
27
+
28
+ private
29
+
30
+ def stringable_attributes
31
+ self.class._attributes
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Defaults are used as the value when then attribute is unspecified.
4
+ module Spicerack
5
+ module Objects
6
+ module Defaults
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :_defaults, instance_writer: false, default: {}
11
+ end
12
+
13
+ class_methods do
14
+ def inherited(base)
15
+ dup = _defaults.dup
16
+ base._defaults = dup.each { |k, v| dup[k] = v.dup }
17
+ super
18
+ end
19
+
20
+ private
21
+
22
+ def define_default(attribute, static: nil, &block)
23
+ _defaults[attribute] = Value.new(static: static, &block)
24
+ end
25
+ end
26
+
27
+ class Value
28
+ def initialize(static: nil, &block)
29
+ @value = (static.nil? && block_given?) ? block : static
30
+ end
31
+
32
+ def value
33
+ (@value.respond_to?(:call) ? instance_eval(&@value) : @value).dup
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Options describe input which may be provided to define or override default values.
4
+ module Spicerack
5
+ module Objects
6
+ module Options
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :_options, instance_writer: false, default: []
11
+
12
+ set_callback :initialize, :after do
13
+ _options.each do |option|
14
+ next unless _defaults.key?(option)
15
+
16
+ public_send("#{option}=".to_sym, _defaults[option].value) if public_send(option).nil?
17
+ end
18
+ end
19
+ end
20
+
21
+ class_methods do
22
+ def inherited(base)
23
+ base._options = _options.dup
24
+ super
25
+ end
26
+
27
+ private
28
+
29
+ def option(option, default: nil, &block)
30
+ _options << option
31
+ define_attribute option
32
+ define_default option, static: default, &block
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spicerack
4
+ class RootObject
5
+ include ActiveSupport::Callbacks
6
+ include ShortCircuIt
7
+ include Technologic
8
+ include Tablesalt::StringableObject
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests usage of `.argument`
4
+ #
5
+ # class Example < Spicerack::InputObject
6
+ # argument :foo
7
+ # argument :bar, allow_nil: false
8
+ # end
9
+ #
10
+ # RSpec.describe Example, type: :input_object do
11
+ # subject { described_class.new(**input) }
12
+ #
13
+ # let(:input) { {} }
14
+ #
15
+ # it { is_expected.to define_argument :foo }
16
+ # it { is_expected.to define_argument :bar, allow_nil: false }
17
+ # end
18
+
19
+ RSpec::Matchers.define :define_argument do |argument, allow_nil: true|
20
+ match { |instance| expect(instance._arguments[argument]).to eq(allow_nil: allow_nil) }
21
+ description { "define argument #{argument}" }
22
+ failure_message do
23
+ "expected #{described_class} to define argument #{argument} #{prohibit_nil_description unless allow_nil}"
24
+ end
25
+
26
+ def prohibit_nil_description
27
+ "and prohibit a nil value"
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests usage of `.attribute`
4
+ #
5
+ # class Example < Spicerack::InputObject
6
+ # attribute :foo
7
+ # end
8
+ #
9
+ # RSpec.describe Example, type: :input_object do
10
+ # subject { described_class.new(**input) }
11
+ #
12
+ # let(:input) { {} }
13
+ #
14
+ # it { is_expected.to define_attribute :foo }
15
+ # end
16
+
17
+ RSpec::Matchers.define :define_attribute do |attribute|
18
+ match { |instance| expect(instance._attributes).to include attribute }
19
+ description { "define attribute #{attribute}" }
20
+ failure_message { "expected #{described_class} to defines attribute #{attribute}" }
21
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests usage of `.option`
4
+ #
5
+ # class Example < Spicerack::InputObject
6
+ # option :foo
7
+ # option :bar, default: :baz
8
+ # option(:gaz) { :haz }
9
+ # end
10
+ #
11
+ # RSpec.describe Example, type: :input_object do
12
+ # subject { described_class.new(**input) }
13
+ #
14
+ # let(:input) { {} }
15
+ #
16
+ # it { is_expected.to define_option :foo }
17
+ # it { is_expected.to define_option :bar, default: :baz }
18
+ # it { is_expected.to define_option :gaz, default: :haz }
19
+ # end
20
+
21
+ RSpec::Matchers.define :define_option do |option, default: nil|
22
+ match do |instance|
23
+ expect(instance._defaults[option]&.value).to eq default
24
+ expect(instance._options).to include option
25
+ end
26
+ description { "define option #{option}" }
27
+ failure_message { "expected #{described_class} to define option #{option} #{for_default(default)}" }
28
+
29
+ def for_default(default)
30
+ return "without a default value" if default.nil?
31
+
32
+ "with default value #{default}"
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "custom_matchers/define_argument"
4
+ require_relative "custom_matchers/define_attribute"
5
+ require_relative "custom_matchers/define_field"
6
+ require_relative "custom_matchers/define_option"
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples_for "an input object with a class collection attribute" do |method, collection|
4
+ subject(:define) { example_input_object_class.__send__(method, value) }
5
+
6
+ let(:value) { Faker::Lorem.word.to_sym }
7
+
8
+ before do
9
+ allow(example_input_object_class).to receive(:define_default).and_call_original
10
+ allow(example_input_object_class).to receive(:define_attribute).and_call_original
11
+ end
12
+
13
+ describe "defines value" do
14
+ let(:default) { Faker::Lorem.word }
15
+
16
+ shared_examples_for "an value is defined" do
17
+ it "adds to _values" do
18
+ expect { define }.to change { example_input_object_class.public_send(collection) }.from([]).to([ value ])
19
+ end
20
+ end
21
+
22
+ context "when no block is given" do
23
+ subject(:define) { example_input_object_class.__send__(method, value, default: default) }
24
+
25
+ it_behaves_like "an value is defined"
26
+
27
+ it "defines an static default" do
28
+ define
29
+ expect(example_input_object_class).to have_received(:define_default).with(value, static: default)
30
+ end
31
+ end
32
+
33
+ context "when a block is given" do
34
+ subject(:define) { example_input_object_class.__send__(method, value, default: default, &block) }
35
+
36
+ let(:block) do
37
+ ->(_) { :block }
38
+ end
39
+
40
+ shared_examples_for "values are handed off to define_default" do
41
+ it "calls define_default" do
42
+ define
43
+ expect(example_input_object_class).to have_received(:define_default).with(value, static: default, &block)
44
+ end
45
+ end
46
+
47
+ context "with a static default" do
48
+ it_behaves_like "values are handed off to define_default"
49
+ end
50
+
51
+ context "without a static default" do
52
+ let(:default) { nil }
53
+
54
+ it_behaves_like "values are handed off to define_default"
55
+ end
56
+ end
57
+ end
58
+
59
+ it "defines an attribute" do
60
+ define
61
+ expect(example_input_object_class).to have_received(:define_attribute).with(value)
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared_examples/an_input_object_with_a_class_collection_attribute"
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Shoulda::Matchers::ActiveModel)
4
+ RSpec.configure do |config|
5
+ config.include(Shoulda::Matchers::ActiveModel, type: :input_object)
6
+ config.include(Shoulda::Matchers::ActiveModel, type: :input_model)
7
+ end
8
+ end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rspice"
4
- require "instructor/spec_helper"
5
4
  require "redis_hash/spec_helper"
6
5
 
7
- require_relative "custom_matchers"
6
+ require_relative "rspec/custom_matchers"
7
+ require_relative "rspec/shared_examples"
8
+ require_relative "rspec/shoulda_matcher_helper"
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Spicerack
4
4
  # This constant is managed by spicerack
5
- VERSION = "0.12.0"
5
+ VERSION = "0.13.0"
6
6
  end
data/lib/spicerack.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support"
4
+ require "active_support/core_ext/class/attribute"
5
+
3
6
  require "spicerack/version"
4
7
 
5
8
  require "tablesalt"
@@ -9,15 +12,14 @@ require "short_circu_it"
9
12
 
10
13
  require "technologic"
11
14
 
12
- require "root_object"
13
-
14
- require "ascriptor"
15
- require "instructor"
16
-
17
15
  require "redis_hash"
18
16
 
19
17
  require "spicerack/array_index"
20
18
  require "spicerack/hash_model"
21
19
  require "spicerack/redis_model"
20
+ require "spicerack/root_object"
21
+ require "spicerack/attribute_object"
22
+ require "spicerack/input_object"
23
+ require "spicerack/input_model"
22
24
 
23
25
  module Spicerack; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spicerack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Garside
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-05-28 00:00:00.000000000 Z
12
+ date: 2019-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: around_the_world
@@ -17,112 +17,70 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 0.12.0
20
+ version: 0.13.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 0.12.0
28
- - !ruby/object:Gem::Dependency
29
- name: ascriptor
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - '='
33
- - !ruby/object:Gem::Version
34
- version: 0.12.0
35
- type: :runtime
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - '='
40
- - !ruby/object:Gem::Version
41
- version: 0.12.0
42
- - !ruby/object:Gem::Dependency
43
- name: instructor
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - '='
47
- - !ruby/object:Gem::Version
48
- version: 0.12.0
49
- type: :runtime
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - '='
54
- - !ruby/object:Gem::Version
55
- version: 0.12.0
27
+ version: 0.13.0
56
28
  - !ruby/object:Gem::Dependency
57
29
  name: redis_hash
58
30
  requirement: !ruby/object:Gem::Requirement
59
31
  requirements:
60
32
  - - '='
61
33
  - !ruby/object:Gem::Version
62
- version: 0.12.0
63
- type: :runtime
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - '='
68
- - !ruby/object:Gem::Version
69
- version: 0.12.0
70
- - !ruby/object:Gem::Dependency
71
- name: root_object
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - '='
75
- - !ruby/object:Gem::Version
76
- version: 0.12.0
34
+ version: 0.13.0
77
35
  type: :runtime
78
36
  prerelease: false
79
37
  version_requirements: !ruby/object:Gem::Requirement
80
38
  requirements:
81
39
  - - '='
82
40
  - !ruby/object:Gem::Version
83
- version: 0.12.0
41
+ version: 0.13.0
84
42
  - !ruby/object:Gem::Dependency
85
43
  name: short_circu_it
86
44
  requirement: !ruby/object:Gem::Requirement
87
45
  requirements:
88
46
  - - '='
89
47
  - !ruby/object:Gem::Version
90
- version: 0.12.0
48
+ version: 0.13.0
91
49
  type: :runtime
92
50
  prerelease: false
93
51
  version_requirements: !ruby/object:Gem::Requirement
94
52
  requirements:
95
53
  - - '='
96
54
  - !ruby/object:Gem::Version
97
- version: 0.12.0
55
+ version: 0.13.0
98
56
  - !ruby/object:Gem::Dependency
99
57
  name: technologic
100
58
  requirement: !ruby/object:Gem::Requirement
101
59
  requirements:
102
60
  - - '='
103
61
  - !ruby/object:Gem::Version
104
- version: 0.12.0
62
+ version: 0.13.0
105
63
  type: :runtime
106
64
  prerelease: false
107
65
  version_requirements: !ruby/object:Gem::Requirement
108
66
  requirements:
109
67
  - - '='
110
68
  - !ruby/object:Gem::Version
111
- version: 0.12.0
69
+ version: 0.13.0
112
70
  - !ruby/object:Gem::Dependency
113
71
  name: tablesalt
114
72
  requirement: !ruby/object:Gem::Requirement
115
73
  requirements:
116
74
  - - '='
117
75
  - !ruby/object:Gem::Version
118
- version: 0.12.0
76
+ version: 0.13.0
119
77
  type: :runtime
120
78
  prerelease: false
121
79
  version_requirements: !ruby/object:Gem::Requirement
122
80
  requirements:
123
81
  - - '='
124
82
  - !ruby/object:Gem::Version
125
- version: 0.12.0
83
+ version: 0.13.0
126
84
  - !ruby/object:Gem::Dependency
127
85
  name: bundler
128
86
  requirement: !ruby/object:Gem::Requirement
@@ -241,28 +199,28 @@ dependencies:
241
199
  requirements:
242
200
  - - '='
243
201
  - !ruby/object:Gem::Version
244
- version: 0.12.0
202
+ version: 0.13.0
245
203
  type: :development
246
204
  prerelease: false
247
205
  version_requirements: !ruby/object:Gem::Requirement
248
206
  requirements:
249
207
  - - '='
250
208
  - !ruby/object:Gem::Version
251
- version: 0.12.0
209
+ version: 0.13.0
252
210
  - !ruby/object:Gem::Dependency
253
211
  name: spicerack-styleguide
254
212
  requirement: !ruby/object:Gem::Requirement
255
213
  requirements:
256
214
  - - '='
257
215
  - !ruby/object:Gem::Version
258
- version: 0.12.0
216
+ version: 0.13.0
259
217
  type: :development
260
218
  prerelease: false
261
219
  version_requirements: !ruby/object:Gem::Requirement
262
220
  requirements:
263
221
  - - '='
264
222
  - !ruby/object:Gem::Version
265
- version: 0.12.0
223
+ version: 0.13.0
266
224
  description: This collection of gems will spice up your rails and kick your rubies
267
225
  up a notch. Bam!
268
226
  email:
@@ -276,10 +234,24 @@ files:
276
234
  - README.md
277
235
  - lib/spicerack.rb
278
236
  - lib/spicerack/array_index.rb
279
- - lib/spicerack/custom_matchers.rb
280
- - lib/spicerack/custom_matchers/define_field.rb
237
+ - lib/spicerack/attribute_object.rb
281
238
  - lib/spicerack/hash_model.rb
239
+ - lib/spicerack/input_model.rb
240
+ - lib/spicerack/input_object.rb
241
+ - lib/spicerack/objects/arguments.rb
242
+ - lib/spicerack/objects/attributes.rb
243
+ - lib/spicerack/objects/defaults.rb
244
+ - lib/spicerack/objects/options.rb
282
245
  - lib/spicerack/redis_model.rb
246
+ - lib/spicerack/root_object.rb
247
+ - lib/spicerack/rspec/custom_matchers.rb
248
+ - lib/spicerack/rspec/custom_matchers/define_argument.rb
249
+ - lib/spicerack/rspec/custom_matchers/define_attribute.rb
250
+ - lib/spicerack/rspec/custom_matchers/define_field.rb
251
+ - lib/spicerack/rspec/custom_matchers/define_option.rb
252
+ - lib/spicerack/rspec/shared_examples.rb
253
+ - lib/spicerack/rspec/shared_examples/an_input_object_with_a_class_collection_attribute.rb
254
+ - lib/spicerack/rspec/shoulda_matcher_helper.rb
283
255
  - lib/spicerack/spec_helper.rb
284
256
  - lib/spicerack/version.rb
285
257
  homepage: https://www.freshly.com/
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "custom_matchers/define_field"