opium 1.2.0 → 1.2.1

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: 3bbf318b07ca2a59bb917e4495abda45117aa588
4
- data.tar.gz: c85609101238b4c6142df1d421a90e33e9fbf38a
3
+ metadata.gz: ecd9332e3c5b659bcb498c22bd08e109dab91b84
4
+ data.tar.gz: e9f1feaa43921e23339c2ea17905ed6f9470e796
5
5
  SHA512:
6
- metadata.gz: 661189662f2ac5cdeb1b0dcb87d32ca291bdb4e888a108e7669153758160ced3f4aa06e961e625544e87c4e7d66a514e588607f0338f81793ee7029ee5266093
7
- data.tar.gz: 76ef297dfd71c71a063140c88f11b48bfb6326b6ca31d333155ac9d6cdbb0917c181d337a7e67adb2038af6261cf4387644ed98211208e932d2e25d9346a3b47
6
+ metadata.gz: 4aa3f212aa8ab6d3a23994909f05d34df0aa7b7b767fb192946a7a2ed1f764758ad594b3ea199bf086296460cc2aeebda4864c62b1dd7911bcdcf7e5b8aeb1fb
7
+ data.tar.gz: ba8ccb1d384ead2549edfa4d58f9c8d852812a1081d20b301aee0c82ff742f28cbd2bec45f5a652e2474047ed3c4c8c333b235c70473d322953ba4b89839968e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.2.1
2
+ ### New Features
3
+ - Fieldable should now be refactored to be more readable and less cluttered; .field, in particular, has been split up into a set of algorithmic steps.
4
+ - #45: If GlobalID is defined, it is automatically included into all models.
5
+
6
+ ### Resolved Issues
7
+ - #46: Relations should now more gracefully handle conversions from nil and Array.
8
+
1
9
  ## 1.2.0
2
10
  ### New Features
3
11
  - #33: Model associations.
@@ -17,6 +17,10 @@ module Opium
17
17
  end
18
18
  end
19
19
 
20
+ def contextual_default_value( context = nil)
21
+ type.to_ruby( default( context ) )
22
+ end
23
+
20
24
  def readonly?
21
25
  self.readonly == true
22
26
  end
@@ -13,31 +13,10 @@ module Opium
13
13
 
14
14
  module ClassMethods
15
15
  def field( name, options = {} )
16
- name = name.to_sym
17
- fields[name] = Field.new( name, options[:type] || Object, options[:default], options[:readonly] || false, options[:as] )
18
- ruby_canonical_field_names[name] = ruby_canonical_field_names[fields[name].name_to_parse] = name.to_s
19
- parse_canonical_field_names[name] = parse_canonical_field_names[fields[name].name_to_parse] = fields[name].name_to_parse.to_s
20
- class_eval do
21
- define_attribute_methods [name]
22
- define_method(name) do
23
- self.attributes[name]
24
- end
25
- end
26
- unless self.respond_to? "#{name}="
27
- class_eval do
28
- define_method("#{name}=") do |value|
29
- converted = self.class.fields[name].type.to_ruby(value)
30
- send( "#{name}_will_change!" ) unless self.attributes[name] == converted
31
- if self.class.fields[name].relation?
32
- converted.owner ||= self
33
- converted.metadata ||= self.class.relations[name]
34
- end
35
- self.attributes[name] = converted
36
- end
37
- send(:private, "#{name}=") if options[:readonly]
38
- end
16
+ create_field_from( name.to_sym, options ).tap do |field|
17
+ create_field_getter_for( field )
18
+ create_field_setter_for( field )
39
19
  end
40
- fields[name]
41
20
  end
42
21
 
43
22
  def has_field?( field_name )
@@ -47,19 +26,53 @@ module Opium
47
26
  alias_method :field?, :has_field?
48
27
 
49
28
  def fields
50
- @fields ||= ActiveSupport::HashWithIndifferentAccess.new
29
+ @fields ||= {}.with_indifferent_access
51
30
  end
52
31
 
53
32
  def ruby_canonical_field_names
54
- @ruby_canonical_field_names ||= ActiveSupport::HashWithIndifferentAccess.new
33
+ @ruby_canonical_field_names ||= {}.with_indifferent_access
55
34
  end
56
35
 
57
36
  def parse_canonical_field_names
58
- @parse_canonical_field_names ||= ActiveSupport::HashWithIndifferentAccess.new
37
+ @parse_canonical_field_names ||= {}.with_indifferent_access
59
38
  end
60
39
 
61
40
  def default_attributes( context = nil )
62
- fields.transform_values {|field| field.type.to_ruby field.default( context ) }.with_indifferent_access
41
+ fields.transform_values {|field| field.contextual_default_value( context ) }.with_indifferent_access
42
+ end
43
+
44
+ private
45
+
46
+ def create_field_from( name, options )
47
+ field = Field.new( name, options[:type] || Object, options[:default], options[:readonly] || false, options[:as] )
48
+ ruby_canonical_field_names[name] = ruby_canonical_field_names[field.name_to_parse] = name.to_s
49
+ parse_canonical_field_names[name] = parse_canonical_field_names[field.name_to_parse] = field.name_to_parse.to_s
50
+ fields[name] = field
51
+ end
52
+
53
+ def create_field_getter_for( field )
54
+ class_eval do
55
+ define_attribute_methods [field.name]
56
+ define_method(field.name) do
57
+ self.attributes[field.name]
58
+ end
59
+ end
60
+ end
61
+
62
+ def create_field_setter_for( field )
63
+ class_eval do
64
+ define_method("#{ field.name }=") do |value|
65
+ converted = field.type.to_ruby(value)
66
+ send( "#{ field.name }_will_change!" ) unless self.attributes[field.name] == converted
67
+ if field.relation?
68
+ converted = field.contextual_default_value( self ) unless converted
69
+ converted.owner ||= self
70
+ converted.metadata ||= self.class.relations[field.name]
71
+ end
72
+ self.attributes[field.name] = converted
73
+ end
74
+ send(:private, "#{ field.name }=") if field.readonly?
75
+ end
63
76
  end
64
77
  end
65
78
  end
@@ -5,46 +5,55 @@ module Opium
5
5
 
6
6
  class << self
7
7
  def to_parse( object )
8
- class_name =
9
- case object
10
- when Hash
11
- fetch_hash_key_from( object, 'class_name' ) || fetch_hash_key_from( object, 'model_name' )
12
- when String, Symbol
13
- object
14
- when is_descendant.curry[Opium::Model]
15
- object.model_name
16
- when self
17
- object.class_name
18
- else
19
- fail ArgumentError, "could not convert #{ object.inspect } to a parse Relation hash"
20
- end
8
+ class_name = determine_class_name_from( object, 'parse Relation hash' )
21
9
  fail ArgumentError, "could not determine class_name from #{ object.inspect }" unless class_name
22
10
  { __type: 'Relation', className: class_name }.with_indifferent_access
23
11
  end
24
12
 
25
13
  def to_ruby( object )
26
- return if object.nil?
14
+ return if object.nil? || object == []
27
15
  return object if object.is_a? self
28
- class_name =
16
+ class_name = determine_class_name_from( object, self.name )
17
+ new( class_name ).tap do |relation|
29
18
  case object
30
- when Hash
31
- fetch_hash_key_from( object, 'class_name' ) || fetch_hash_key_from( object, 'model_name' )
32
- when String, Symbol
33
- object
19
+ when String
34
20
  when is_descendant.curry[Opium::Model]
35
- object.model_name
36
- else
37
- fail ArgumentError, "could not convert #{ object.inspect } to a Opium::Model::Relation"
21
+ relation.push object
22
+ when contains_descendant.curry[Opium::Model]
23
+ object.select(&is_descendant.curry[Opium::Model]).each {|model| relation.push model}
38
24
  end
39
- new( class_name )
25
+ end
40
26
  end
41
27
 
42
28
  private
43
29
 
30
+ def determine_class_name_from( object, context )
31
+ case object
32
+ when Hash
33
+ fetch_hash_key_from( object, 'class_name' ) || fetch_hash_key_from( object, 'model_name' )
34
+ when String, Symbol
35
+ object
36
+ when is_descendant.curry[Opium::Model]
37
+ object.model_name
38
+ when contains_descendant.curry[Opium::Model]
39
+ object.select(&is_descendant.curry[Opium::Model]).first.model_name
40
+ when self
41
+ object.class_name
42
+ else
43
+ fail ArgumentError, "could not convert #{ object.inspect } to a #{ context }"
44
+ end
45
+ end
46
+
44
47
  def is_descendant
45
48
  @is_descendant ||= ->( expected_type, object ) { ( object.is_a?( Class ) ? object : object.class ) <= expected_type }
46
49
  end
47
50
 
51
+ def contains_descendant
52
+ @contains_descendant ||= ->( expected_type, object ) do
53
+ object.is_a?( Enumerable ) ? object.any?( &is_descendant.curry[expected_type] ) : is_descendant.curry[expected_type]
54
+ end
55
+ end
56
+
48
57
  def fetch_hash_key_from( hash, key )
49
58
  snake_case_key = key.to_s.underscore
50
59
  lower_camel_key = key.to_s.camelcase(:lower)
data/lib/opium/model.rb CHANGED
@@ -40,6 +40,7 @@ module Opium
40
40
  include Inheritable
41
41
  include Batchable
42
42
  include Relatable
43
+ include GlobalID if defined?( GlobalID )
43
44
  end
44
45
 
45
46
  def initialize( attributes = {} )
data/lib/opium/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Opium
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -161,6 +161,34 @@ describe Opium::Model::Relatable do
161
161
  it { expect( subject.comments ).to_not be_empty }
162
162
  it { expect( subject.comments.owner ).to eq subject }
163
163
  end
164
+
165
+ context "when setting a model's relation" do
166
+ subject { Article.new id: 'abcd1234' }
167
+ let(:result) { subject.comments = new_value; subject.comments }
168
+
169
+ context 'to nil' do
170
+ let(:new_value) { nil }
171
+
172
+ it { expect { result }.to_not raise_exception }
173
+ it { expect( result ).to be_a Opium::Model::Relation }
174
+ end
175
+
176
+ context 'to []' do
177
+ let(:new_value) { [] }
178
+
179
+ it { expect { result }.to_not raise_exception }
180
+ it { expect( result ).to be_a Opium::Model::Relation }
181
+ end
182
+
183
+ context 'to an array of models' do
184
+ let(:comment) { Comment.new }
185
+ let(:new_value) { [ comment ] }
186
+
187
+ it { expect { result }.to_not raise_exception }
188
+ it { expect( result ).to be_a Opium::Model::Relation }
189
+ it { expect( result ).to include( comment ) }
190
+ end
191
+ end
164
192
  end
165
193
 
166
194
  describe '.has_one' do
@@ -87,6 +87,22 @@ describe Opium::Model::Relation do
87
87
  it { expect( result ).to be_nil }
88
88
  end
89
89
 
90
+ context 'with an empty array' do
91
+ let(:convert_from) { [] }
92
+
93
+ it { expect { result }.to_not raise_exception }
94
+ it { expect( result ).to be_nil }
95
+ end
96
+
97
+ context 'with an array of RelatedClass' do
98
+ let(:related_object) { RelatedClass.new title: 'a related object' }
99
+ let(:convert_from) { [ related_object ] }
100
+
101
+ it { expect { result }.to_not raise_exception }
102
+ it { expect( result ).to be_a described_class }
103
+ it { expect( result ).to include( related_object ) }
104
+ end
105
+
90
106
  context 'with any unconvertable value' do
91
107
  let(:convert_from) { 42 }
92
108
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opium
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Bowers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-20 00:00:00.000000000 Z
11
+ date: 2015-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler