instructor 0.8.1 → 0.8.2
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/lib/instructor/base.rb +25 -0
- data/lib/instructor/concerns/arguments.rb +36 -0
- data/lib/instructor/concerns/attributes.rb +31 -0
- data/lib/instructor/concerns/callbacks.rb +13 -0
- data/lib/instructor/concerns/core.rb +17 -0
- data/lib/instructor/concerns/defaults.rb +36 -0
- data/lib/instructor/concerns/options.rb +35 -0
- data/lib/instructor/concerns/string.rb +40 -0
- data/lib/instructor/custom_matchers/define_argument.rb +29 -0
- data/lib/instructor/custom_matchers/define_attribute.rb +21 -0
- data/lib/instructor/custom_matchers/define_option.rb +34 -0
- data/lib/instructor/custom_matchers.rb +5 -0
- data/lib/instructor/shoulda_matcher_helper.rb +7 -0
- data/lib/instructor/spec_helper.rb +4 -0
- data/lib/instructor/version.rb +1 -1
- data/lib/instructor.rb +7 -3
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e3514be63395d163fbbffaf7f7bbe99495e537bf8ac54ba3def4d521a034155
|
4
|
+
data.tar.gz: 7e12e5505209f56e7d1040a3d095155c376c29bcb7afc713b6080897785cd215
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ad95ed38f49b3ae1b3cb5ecdc96e907c90c879a0ef9143083beedf16df8e08c0a4f8bf7a64ea508c59738567ce90c921aebf9fd1b56caeb612dbd848dc43ebf
|
7
|
+
data.tar.gz: 44dcf5d4a5ba8cf0a89fb2015b6fa7f5f13f8ac5ea4e379d7d70951056a604339cc44551e6fb399b3cc37ec2206e4ca6d8eaa450fcd8798434db4a80c31dc94c
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "concerns/callbacks"
|
4
|
+
require_relative "concerns/defaults"
|
5
|
+
require_relative "concerns/attributes"
|
6
|
+
require_relative "concerns/arguments"
|
7
|
+
require_relative "concerns/options"
|
8
|
+
require_relative "concerns/core"
|
9
|
+
require_relative "concerns/string"
|
10
|
+
|
11
|
+
module Instructor
|
12
|
+
class Base
|
13
|
+
include ShortCircuIt
|
14
|
+
include Technologic
|
15
|
+
include ActiveModel::Model
|
16
|
+
include ActiveModel::Validations::Callbacks
|
17
|
+
include Instructor::Callbacks
|
18
|
+
include Instructor::Defaults
|
19
|
+
include Instructor::Attributes
|
20
|
+
include Instructor::Arguments
|
21
|
+
include Instructor::Options
|
22
|
+
include Instructor::Core
|
23
|
+
include Instructor::String
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Arguments describe input required by an Instructor.
|
4
|
+
module Instructor
|
5
|
+
module Arguments
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_arguments, instance_writer: false, default: {}
|
10
|
+
set_callback :initialize, :after do
|
11
|
+
missing_arguments = _arguments.reject do |argument, options|
|
12
|
+
options[:allow_nil] ? input.key?(argument) : !input[argument].nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
missing = missing_arguments.keys
|
16
|
+
|
17
|
+
raise ArgumentError, "Missing #{"argument".pluralize(missing.length)}: #{missing.join(", ")}" if missing.any?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class_methods do
|
22
|
+
def inherited(base)
|
23
|
+
dup = _arguments.dup
|
24
|
+
base._arguments = dup.each { |k, v| dup[k] = v.dup }
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def argument(argument, allow_nil: true)
|
31
|
+
_arguments[argument] = { allow_nil: allow_nil }
|
32
|
+
define_attribute argument
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An Instructor's attributes provide accessors to the input data it was initialized with.
|
4
|
+
module Instructor
|
5
|
+
module Attributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ActiveModel::AttributeMethods
|
10
|
+
|
11
|
+
class_attribute :_attributes, instance_writer: false, default: []
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def inherited(base)
|
16
|
+
base._attributes = _attributes.dup
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def define_attribute(attribute)
|
23
|
+
_attributes << attribute
|
24
|
+
|
25
|
+
attr_accessor attribute
|
26
|
+
define_attribute_methods attribute
|
27
|
+
end
|
28
|
+
alias_method :attribute, :define_attribute
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Callbacks provide an extensible mechanism for hooking into an Instructor.
|
4
|
+
module Instructor
|
5
|
+
module Callbacks
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ActiveSupport::Callbacks
|
10
|
+
define_callbacks :initialize
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An Instructor accepts input represented by arguments and options which initialize it.
|
4
|
+
module Instructor
|
5
|
+
module Core
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
attr_reader :input
|
9
|
+
|
10
|
+
def initialize(**input)
|
11
|
+
@input = input
|
12
|
+
run_callbacks(:initialize) do
|
13
|
+
input.each { |key, value| __send__("#{key}=".to_sym, value) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Defaults allow attributes to be initialized with a value if none have been provided.
|
4
|
+
module Instructor
|
5
|
+
module Defaults
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_defaults, instance_writer: false, default: {}
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def inherited(base)
|
14
|
+
dup = _defaults.dup
|
15
|
+
base._defaults = dup.each { |k, v| dup[k] = v.dup }
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def define_default(attribute, static: nil, &block)
|
22
|
+
_defaults[attribute] = Value.new(static: static, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Value
|
27
|
+
def initialize(static: nil, &block)
|
28
|
+
@value = (static.nil? && block_given?) ? block : static
|
29
|
+
end
|
30
|
+
|
31
|
+
def value
|
32
|
+
(@value.respond_to?(:call) ? instance_eval(&@value) : @value).dup
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Options describe input which may be provided to define or override default values.
|
4
|
+
module Instructor
|
5
|
+
module Options
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_options, instance_writer: false, default: []
|
10
|
+
|
11
|
+
set_callback :initialize, :after do
|
12
|
+
_options.each do |option|
|
13
|
+
next unless _defaults.key?(option)
|
14
|
+
|
15
|
+
public_send("#{option}=".to_sym, _defaults[option].value) if public_send(option).nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class_methods do
|
21
|
+
def inherited(base)
|
22
|
+
base._options = _options.dup
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def option(option, default: nil, &block)
|
29
|
+
_options << option
|
30
|
+
define_attribute option
|
31
|
+
define_default option, static: default, &block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Formats the Instructor as a string.
|
4
|
+
module Instructor
|
5
|
+
module String
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
string_for(__method__)
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
string_for(__method__)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def stringable_attributes
|
19
|
+
self.class._attributes
|
20
|
+
end
|
21
|
+
|
22
|
+
def string_for(method)
|
23
|
+
"#<#{self.class.name} #{attribute_string(method)}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute_string(method)
|
27
|
+
stringable_attribute_values.map { |attribute, value| "#{attribute}=#{value.public_send(method)}" }.join(" ")
|
28
|
+
end
|
29
|
+
|
30
|
+
def stringable_attribute_values
|
31
|
+
stringable_attributes.each_with_object({}) { |attribute, result| result[attribute] = safe_send(attribute) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def safe_send(method)
|
35
|
+
public_send(method)
|
36
|
+
rescue StandardError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher that tests usage of `.argument`
|
4
|
+
#
|
5
|
+
# class Example < Instructor::Base
|
6
|
+
# argument :foo
|
7
|
+
# argument :bar, allow_nil: false
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# RSpec.describe Example, type: :instructor 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 < Instructor::Base
|
6
|
+
# attribute :foo
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# RSpec.describe Example, type: :instructor 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 < Instructor::Base
|
6
|
+
# option :foo
|
7
|
+
# option :bar, default: :baz
|
8
|
+
# option(:gaz) { :haz }
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# RSpec.describe Example, type: :instructor 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
|
data/lib/instructor/version.rb
CHANGED
data/lib/instructor.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support"
|
4
|
+
require "active_model"
|
5
|
+
|
6
|
+
require "short_circu_it"
|
7
|
+
require "technologic"
|
4
8
|
|
5
9
|
require "instructor/version"
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
end
|
11
|
+
require "instructor/base"
|
12
|
+
|
13
|
+
module Instructor; end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: instructor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Garside
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-05-
|
11
|
+
date: 2019-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 5.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.1
|
27
41
|
description: Input structure base object for capturing and validating input with a
|
28
42
|
nice DSL
|
29
43
|
email:
|
@@ -35,6 +49,20 @@ files:
|
|
35
49
|
- LICENSE.txt
|
36
50
|
- README.md
|
37
51
|
- lib/instructor.rb
|
52
|
+
- lib/instructor/base.rb
|
53
|
+
- lib/instructor/concerns/arguments.rb
|
54
|
+
- lib/instructor/concerns/attributes.rb
|
55
|
+
- lib/instructor/concerns/callbacks.rb
|
56
|
+
- lib/instructor/concerns/core.rb
|
57
|
+
- lib/instructor/concerns/defaults.rb
|
58
|
+
- lib/instructor/concerns/options.rb
|
59
|
+
- lib/instructor/concerns/string.rb
|
60
|
+
- lib/instructor/custom_matchers.rb
|
61
|
+
- lib/instructor/custom_matchers/define_argument.rb
|
62
|
+
- lib/instructor/custom_matchers/define_attribute.rb
|
63
|
+
- lib/instructor/custom_matchers/define_option.rb
|
64
|
+
- lib/instructor/shoulda_matcher_helper.rb
|
65
|
+
- lib/instructor/spec_helper.rb
|
38
66
|
- lib/instructor/version.rb
|
39
67
|
homepage: https://www.freshly.com
|
40
68
|
licenses:
|