attributor 5.0.1 → 5.0.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 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