spicerack 0.12.0 → 0.13.0

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 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"