instructor 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|