attributor 5.0.1 → 5.0.2

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
  SHA1:
3
- metadata.gz: e92945289cab58f688cbad5df9d032fa684537b3
4
- data.tar.gz: 53c15208c223f49ae8c05fdeebb962e31cc9c63e
3
+ metadata.gz: 1b19e77ce8b1772454840886e1a1bda5bc248c63
4
+ data.tar.gz: 32f9bca389ef6c112a7d8edf81cf8adc001ca2fd
5
5
  SHA512:
6
- metadata.gz: 957400d332d6091f4f288c338ca9564004a9ca07b7d50355a8870c004f4419c3879e35c997aa4cdcdd4e92fd1301935c860de9c92318ae94d1d7af9f7208cf72
7
- data.tar.gz: 7a189aa4fac7c5c7263e130a25b52515fb3de6717e652cbc7f5fd00d1c95acf48caaad70771b151b96365e616a9c6e384b873de2abfe68604a2b4b804cc4c0b0
6
+ metadata.gz: dc0fadeddc769124b2b60e717c48d435d88caf15cb6779b0784840c0bdcbbb4efce092fe4648dc8fd14a7b18958c0142dc8e6b52318b44f0b80b0c6f516e4a92
7
+ data.tar.gz: 6bfe838ad696d85db73ee1893a1d4f7739958180cb4f0f1a35ca5e8ff5ebb1a11bdde7bd10c23645623390cde56a6b95e72321ec819579068bac7b56037281c6
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 5.0.2
6
+
7
+ * Introduce the `Dumpable` (empty) module as an interface to indicate that instances of types that include it
8
+ will respond to the `.dump` method, as a way to convert their internal substructure to primitive Ruby objects. * Currently the only two directly dumpable types are Collection and Hash (with the caveat that there are several others that derive from them..i.e., CSV, Model, etc...)
9
+ * The rest of types have `native_types` that are already Ruby primitive Objects.
10
+ * Fixed Hash and Model requirements to treat nil values as missing keys (to be compatible with the `required: true` option on an attribute).
11
+
5
12
  ## 5.0.1
6
13
 
7
14
  * Fix bug that made Struct/Models skip validation of requirements using the `requires` DSL
@@ -7,6 +7,8 @@ require 'digest/sha1'
7
7
 
8
8
  module Attributor
9
9
 
10
+ require_relative 'attributor/dumpable'
11
+
10
12
  require_relative 'attributor/exceptions'
11
13
  require_relative 'attributor/attribute'
12
14
  require_relative 'attributor/type'
@@ -0,0 +1,11 @@
1
+ module Attributor
2
+ module Dumpable
3
+ # Interface denoting that instances of such type respond to .dump as a way to properly
4
+ # serialize its contents into primitive ruby objects.
5
+ # This typically corresponds to non-trivial types that have some sort of substructure
6
+ def dump
7
+ raise NotImplementedError, 'Dumpable requires the implementation of #dump'
8
+ end
9
+
10
+ end
11
+ end
@@ -25,40 +25,41 @@ module Attributor
25
25
  @number = spec[type]
26
26
  end
27
27
  end
28
- def of( *args)
28
+
29
+ def of(*args)
29
30
  @attr_names = args
30
31
  self
31
32
  end
32
33
 
33
- def validate( object,context=Attributor::DEFAULT_ROOT_CONTEXT,_attribute=nil)
34
+ def validate(keys,context=Attributor::DEFAULT_ROOT_CONTEXT,_attribute=nil)
34
35
  result = []
35
36
  case type
36
37
  when :all
37
- rest = attr_names - object.keys
38
+ rest = attr_names - keys
38
39
  unless rest.empty?
39
40
  rest.each do |attr|
40
41
  result.push "Key #{attr} is required for #{Attributor.humanize_context(context)}."
41
42
  end
42
43
  end
43
44
  when :exactly
44
- included = attr_names & object.keys
45
+ included = attr_names & keys
45
46
  unless included.size == number
46
47
  result.push "Exactly #{number} of the following keys #{attr_names} are required for #{Attributor.humanize_context(context)}. Found #{included.size} instead: #{included.inspect}"
47
48
  end
48
49
  when :at_most
49
- rest = attr_names & object.keys
50
+ rest = attr_names & keys
50
51
  if rest.size > number
51
52
  found = rest.empty? ? "none" : rest.inspect
52
53
  result.push "At most #{number} keys out of #{attr_names} can be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
53
54
  end
54
55
  when :at_least
55
- rest = attr_names & object.keys
56
+ rest = attr_names & keys
56
57
  if rest.size < number
57
58
  found = rest.empty? ? "none" : rest.inspect
58
59
  result.push "At least #{number} keys out of #{attr_names} are required to be passed in for #{Attributor.humanize_context(context)}. Found #{found}"
59
60
  end
60
61
  when :exclusive
61
- intersection = attr_names & object.keys
62
+ intersection = attr_names & keys
62
63
  if intersection.size > 1
63
64
  result.push "keys #{intersection.inspect} are mutually exclusive for #{Attributor.humanize_context(context)}."
64
65
  end
@@ -138,4 +139,4 @@ module Attributor
138
139
 
139
140
 
140
141
  end
141
- end
142
+ end
@@ -5,6 +5,7 @@ module Attributor
5
5
 
6
6
  class Collection < Array
7
7
  include Container
8
+ include Dumpable
8
9
 
9
10
  # @param type [Attributor::Type] optional, defines the type of all collection members
10
11
  # @return anonymous class with specified type of collection members
@@ -22,6 +22,7 @@ module Attributor
22
22
 
23
23
  include Container
24
24
  include Enumerable
25
+ include Dumpable
25
26
 
26
27
  class << self
27
28
  attr_reader :key_type, :value_type, :options
@@ -556,6 +557,7 @@ module Attributor
556
557
 
557
558
  def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
558
559
  context = [context] if context.is_a? ::String
560
+ errors = []
559
561
 
560
562
  if self.class.keys.any?
561
563
  extra_keys = @contents.keys - self.class.keys.keys
@@ -565,36 +567,41 @@ module Attributor
565
567
  end
566
568
  end
567
569
 
568
- ret = self.class.keys.each_with_object(Array.new) do |(key, attribute), errors|
570
+ keys_with_values = Array.new
571
+
572
+ self.class.keys.each do |key, attribute|
569
573
  sub_context = self.class.generate_subcontext(context,key)
570
574
 
571
575
  value = @contents[key]
576
+ unless value.nil?
577
+ keys_with_values << key
578
+ end
572
579
 
573
580
  if value.respond_to?(:validating) # really, it's a thing with sub-attributes
574
581
  next if value.validating
575
582
  end
576
583
 
577
- errors.push *attribute.validate(value, sub_context)
584
+ errors.push(*attribute.validate(value, sub_context))
585
+ end
586
+ self.class.requirements.each do |req|
587
+ validation_errors = req.validate(keys_with_values, context)
588
+ errors.push(*validation_errors) unless validation_errors.empty?
578
589
  end
579
590
  else
580
- ret = @contents.each_with_object(Array.new) do |(key, value), errors|
591
+ @contents.each do |key, value|
581
592
  # FIXME: the sub contexts and error messages don't really make sense here
582
593
  unless key_type == Attributor::Object
583
594
  sub_context = context + ["key(#{key.inspect})"]
584
- errors.push *key_attribute.validate(key, sub_context)
595
+ errors.push(*key_attribute.validate(key, sub_context))
585
596
  end
586
597
 
587
598
  unless value_type == Attributor::Object
588
599
  sub_context = context + ["value(#{value.inspect})"]
589
- errors.push *value_attribute.validate(value, sub_context)
600
+ errors.push(*value_attribute.validate(value, sub_context))
590
601
  end
591
602
  end
592
603
  end
593
- self.class.requirements.each_with_object(ret) do |req, errors|
594
- validation_errors = req.validate( @contents , context)
595
- errors.push *validation_errors unless validation_errors.empty?
596
- end
597
- ret
604
+ errors
598
605
  end
599
606
 
600
607
 
@@ -130,22 +130,29 @@ module Attributor
130
130
  @validating = true
131
131
 
132
132
  context = [context] if context.is_a? ::String
133
+ keys_with_values = []
134
+ errors = []
133
135
 
134
- ret = self.class.attributes.each_with_object(Array.new) do |(sub_attribute_name, sub_attribute), errors|
136
+ self.class.attributes.each do |sub_attribute_name, sub_attribute|
135
137
  sub_context = self.class.generate_subcontext(context,sub_attribute_name)
136
138
 
137
139
  value = self.__send__(sub_attribute_name)
140
+ unless value.nil?
141
+ keys_with_values << sub_attribute_name
142
+ end
143
+
138
144
  if value.respond_to?(:validating) # really, it's a thing with sub-attributes
139
145
  next if value.validating
140
146
  end
141
147
 
142
- errors.push *sub_attribute.validate(value, sub_context)
148
+ errors.push(*sub_attribute.validate(value, sub_context))
143
149
  end
144
- self.class.requirements.each_with_object(ret) do |req, errors|
145
- validation_errors = req.validate( @contents , context)
146
- errors.push *validation_errors unless validation_errors.empty?
150
+ self.class.requirements.each do |req|
151
+ validation_errors = req.validate(keys_with_values, context)
152
+ errors.push(*validation_errors) unless validation_errors.empty?
147
153
  end
148
- ret
154
+
155
+ errors
149
156
  ensure
150
157
  @validating = false
151
158
  end
@@ -1,3 +1,3 @@
1
1
  module Attributor
2
- VERSION = '5.0.1'
2
+ VERSION = '5.0.2'
3
3
  end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe 'Dumpable' do
4
+
5
+ context 'for classes forgetting to implement #dump' do
6
+ let(:type) {
7
+ Class.new do
8
+ include Attributor::Dumpable
9
+ end
10
+ }
11
+
12
+ it 'gets an exception' do
13
+ expect{ type.new.dump }.to raise_exception(NotImplementedError)
14
+ end
15
+ end
16
+
17
+ context 'for classes properly implementing #dump' do
18
+ let(:type) {
19
+ Class.new do
20
+ include Attributor::Dumpable
21
+ def dump
22
+ end
23
+ end
24
+ }
25
+
26
+ it 'do not get the base exception' do
27
+ expect{ type.new.dump }.to_not raise_exception
28
+ end
29
+ end
30
+ end
@@ -116,31 +116,31 @@ describe Attributor::HashDSLCompiler do
116
116
 
117
117
  context 'for :all' do
118
118
  let(:arguments){ { all: [:one, :two, :three] } }
119
- let(:value){ {one: 1}}
119
+ let(:value){ [:one] }
120
120
  let(:validation_error){ ["Key two is required for $.", "Key three is required for $."] }
121
121
  it { subject.should include(*validation_error) }
122
122
  end
123
123
  context 'for :exactly' do
124
124
  let(:requirement) { req_class.new(exactly: 1).of(:one,:two) }
125
- let(:value){ {one: 1, two: 2}}
125
+ let(:value){ [:one, :two] }
126
126
  let(:validation_error){ "Exactly 1 of the following keys [:one, :two] are required for $. Found 2 instead: [:one, :two]" }
127
127
  it { subject.should include(validation_error) }
128
128
  end
129
129
  context 'for :at_least' do
130
130
  let(:requirement) { req_class.new(at_least: 2).of(:one,:two,:three) }
131
- let(:value){ {one: 1}}
131
+ let(:value){ [:one] }
132
132
  let(:validation_error){ "At least 2 keys out of [:one, :two, :three] are required to be passed in for $. Found [:one]" }
133
133
  it { subject.should include(validation_error) }
134
134
  end
135
135
  context 'for :at_most' do
136
136
  let(:requirement) { req_class.new(at_most: 1).of(:one,:two,:three) }
137
- let(:value){ {one: 1, two: 2}}
137
+ let(:value){ [:one, :two] }
138
138
  let(:validation_error){ "At most 1 keys out of [:one, :two, :three] can be passed in for $. Found [:one, :two]" }
139
139
  it { subject.should include(validation_error) }
140
140
  end
141
141
  context 'for :exclusive' do
142
142
  let(:arguments){ { exclusive: [:one, :two] } }
143
- let(:value){ {one: 1, two: 2}}
143
+ let(:value){ [:one, :two] }
144
144
  let(:validation_error){ "keys [:one, :two] are mutually exclusive for $." }
145
145
  it { subject.should include(validation_error) }
146
146
  end
@@ -174,4 +174,4 @@ describe Attributor::HashDSLCompiler do
174
174
  end
175
175
  end
176
176
  end
177
- end
177
+ end
@@ -3,13 +3,17 @@ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
3
3
  describe Attributor::BigDecimal do
4
4
  subject(:type) { Attributor::BigDecimal }
5
5
 
6
+ it 'it is not Dumpable' do
7
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
8
+ end
9
+
6
10
  context '.native_type' do
7
11
  its(:native_type) { should be(::BigDecimal) }
8
12
  end
9
13
 
10
14
  context '.example' do
11
15
  its(:example) { should be_a(::BigDecimal) }
12
- it do
16
+ it do
13
17
  ex = type.example
14
18
  end
15
19
  end
@@ -19,7 +23,7 @@ describe Attributor::BigDecimal do
19
23
  it 'returns nil for nil' do
20
24
  type.load(nil).should be(nil)
21
25
  end
22
-
26
+
23
27
  context 'for incoming Float values' do
24
28
  it 'returns the incoming value' do
25
29
  [0.0, -1.0, 1.0, 1e-10, 0.25135].each do |value|
@@ -36,7 +40,7 @@ describe Attributor::BigDecimal do
36
40
  end
37
41
  end
38
42
 
39
- context 'for incoming String values' do
43
+ context 'for incoming String values' do
40
44
  it 'should equal the value' do
41
45
  type.load('0').should eq(0)
42
46
  type.load('100').should eq(100)
@@ -4,6 +4,10 @@ describe Attributor::Boolean do
4
4
 
5
5
  subject(:type) { Attributor::Boolean }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.valid_type?' do
8
12
 
9
13
  context 'for incoming Boolean values' do
@@ -5,6 +5,10 @@ describe Attributor::Class do
5
5
 
6
6
  subject(:type) { Attributor::Class }
7
7
 
8
+ it 'it is not Dumpable' do
9
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
10
+ end
11
+
8
12
  its(:native_type) { should be(::Class) }
9
13
  its(:family) { should == 'string' }
10
14
 
@@ -341,6 +341,10 @@ describe Attributor::Collection do
341
341
  context 'dumping' do
342
342
  let(:type) { Attributor::Collection.of(Cormorant) }
343
343
 
344
+ it 'it is Dumpable' do
345
+ type.new.is_a?(Attributor::Dumpable).should be(true)
346
+ end
347
+
344
348
  subject(:example) { type.example }
345
349
  it 'dumps' do
346
350
  expect {
@@ -4,6 +4,10 @@ describe Attributor::Date do
4
4
 
5
5
  subject(:type) { Attributor::Date }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.native_type' do
8
12
  its(:native_type) { should be(::Date) }
9
13
  end
@@ -20,7 +24,7 @@ describe Attributor::Date do
20
24
  end
21
25
  context 'nil values' do
22
26
  it 'should be nil' do
23
- type.dump(nil).should be_nil
27
+ type.dump(nil).should be_nil
24
28
  end
25
29
  end
26
30
  end
@@ -33,14 +37,14 @@ describe Attributor::Date do
33
37
  end
34
38
 
35
39
  context 'for incoming objects' do
36
-
40
+
37
41
  it "returns correct Date for Time objects" do
38
42
  object = Time.now
39
43
  loaded = type.load(object)
40
44
  loaded.should be_a(::Date)
41
45
  loaded.to_date.should == object.to_date
42
46
  end
43
-
47
+
44
48
  it "returns correct Date for DateTime objects" do
45
49
  object = DateTime.now
46
50
  loaded = type.load(object)
@@ -48,8 +52,8 @@ describe Attributor::Date do
48
52
  loaded.should be(object)
49
53
  end
50
54
 
51
- end
52
-
55
+ end
56
+
53
57
  context 'for incoming strings' do
54
58
 
55
59
  [
@@ -4,6 +4,10 @@ describe Attributor::DateTime do
4
4
 
5
5
  subject(:type) { Attributor::DateTime }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.native_type' do
8
12
  its(:native_type) { should be(::DateTime) }
9
13
  end
@@ -20,7 +24,7 @@ describe Attributor::DateTime do
20
24
  end
21
25
  context 'nil values' do
22
26
  it 'should be nil' do
23
- type.dump(nil).should be_nil
27
+ type.dump(nil).should be_nil
24
28
  end
25
29
  end
26
30
  end
@@ -33,14 +37,14 @@ describe Attributor::DateTime do
33
37
  end
34
38
 
35
39
  context 'for incoming objects' do
36
-
40
+
37
41
  it "returns correct DateTime for Time objects" do
38
42
  object = Time.now
39
43
  loaded = type.load(object)
40
44
  loaded.should be_a(::DateTime)
41
45
  loaded.to_time.should == object
42
46
  end
43
-
47
+
44
48
  it "returns correct DateTime for DateTime objects" do
45
49
  object = DateTime.now
46
50
  loaded = type.load(object)
@@ -48,8 +52,8 @@ describe Attributor::DateTime do
48
52
  loaded.should be( object )
49
53
  end
50
54
 
51
- end
52
-
55
+ end
56
+
53
57
  context 'for incoming strings' do
54
58
 
55
59
  [
@@ -4,6 +4,10 @@ describe Attributor::Float do
4
4
 
5
5
  subject(:type) { Attributor::Float }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.native_type' do
8
12
  its(:native_type) { should be(::Float) }
9
13
  end
@@ -439,6 +439,10 @@ describe Attributor::Hash do
439
439
  let(:value) { {one: 1, two: 2} }
440
440
  let(:opts) { {} }
441
441
 
442
+ it 'it is Dumpable' do
443
+ type.new.is_a?(Attributor::Dumpable).should be(true)
444
+ end
445
+
442
446
  context 'for a simple (untyped) hash' do
443
447
  it 'returns the untouched hash value' do
444
448
  type.dump(value, opts).should eq(value)
@@ -4,6 +4,10 @@ describe Attributor::Integer do
4
4
 
5
5
  subject(:type) { Attributor::Integer }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.example' do
8
12
 
9
13
  context 'when :min and :max are unspecified' do
@@ -4,6 +4,10 @@ describe Attributor::Regexp do
4
4
 
5
5
  subject(:type) { Attributor::Regexp }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  its(:native_type) { should be(::Regexp) }
8
12
  its(:example) { should be_a(::String) }
9
13
  its(:family) { should == 'string' }
@@ -4,6 +4,10 @@ describe Attributor::String do
4
4
 
5
5
  subject(:type) { Attributor::String }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.native_type' do
8
12
  it "returns String" do
9
13
  type.native_type.should be(::String)
@@ -4,6 +4,10 @@ describe Attributor::Time do
4
4
 
5
5
  subject(:type) { Attributor::Time }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  context '.native_type' do
8
12
  its(:native_type) { should be(::Time) }
9
13
  end
@@ -20,7 +24,7 @@ describe Attributor::Time do
20
24
  end
21
25
  context 'nil values' do
22
26
  it 'should be nil' do
23
- type.dump(nil).should be_nil
27
+ type.dump(nil).should be_nil
24
28
  end
25
29
  end
26
30
  end
@@ -33,14 +37,14 @@ describe Attributor::Time do
33
37
  end
34
38
 
35
39
  context 'for incoming objects' do
36
-
40
+
37
41
  it "returns correct Time for DateTime objects" do
38
42
  object = Time.now
39
43
  loaded = type.load(object)
40
44
  loaded.should be_a(::Time)
41
45
  loaded.to_time.should == object
42
46
  end
43
-
47
+
44
48
  it "returns correct Time for DateTime objects" do
45
49
  object = DateTime.now
46
50
  loaded = type.load(object)
@@ -48,8 +52,8 @@ describe Attributor::Time do
48
52
  loaded.should eq(object.to_time)
49
53
  end
50
54
 
51
- end
52
-
55
+ end
56
+
53
57
  context 'for incoming strings' do
54
58
 
55
59
  [
@@ -4,6 +4,10 @@ describe Attributor::URI do
4
4
 
5
5
  subject(:type) { Attributor::URI }
6
6
 
7
+ it 'it is not Dumpable' do
8
+ type.new.is_a?(Attributor::Dumpable).should_not be(true)
9
+ end
10
+
7
11
  its(:native_type) { should be ::URI::Generic }
8
12
 
9
13
  context '.example' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attributor
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-21 00:00:00.000000000 Z
12
+ date: 2016-02-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashie
@@ -285,6 +285,7 @@ files:
285
285
  - lib/attributor/attribute.rb
286
286
  - lib/attributor/attribute_resolver.rb
287
287
  - lib/attributor/dsl_compiler.rb
288
+ - lib/attributor/dumpable.rb
288
289
  - lib/attributor/example_mixin.rb
289
290
  - lib/attributor/exceptions.rb
290
291
  - lib/attributor/extensions/randexp.rb
@@ -322,6 +323,7 @@ files:
322
323
  - spec/attribute_spec.rb
323
324
  - spec/attributor_spec.rb
324
325
  - spec/dsl_compiler_spec.rb
326
+ - spec/dumpable_spec.rb
325
327
  - spec/extras/field_selector/field_selector_spec.rb
326
328
  - spec/families_spec.rb
327
329
  - spec/hash_dsl_compiler_spec.rb
@@ -369,7 +371,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
369
371
  version: '0'
370
372
  requirements: []
371
373
  rubyforge_project:
372
- rubygems_version: 2.4.5.1
374
+ rubygems_version: 2.4.5
373
375
  signing_key:
374
376
  specification_version: 4
375
377
  summary: A powerful attribute and type management library for Ruby
@@ -378,6 +380,7 @@ test_files:
378
380
  - spec/attribute_spec.rb
379
381
  - spec/attributor_spec.rb
380
382
  - spec/dsl_compiler_spec.rb
383
+ - spec/dumpable_spec.rb
381
384
  - spec/extras/field_selector/field_selector_spec.rb
382
385
  - spec/families_spec.rb
383
386
  - spec/hash_dsl_compiler_spec.rb