modelish 0.1.3 → 0.2.0

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.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.2.0 (2012-1-16)
4
+
5
+ * Added support for mapping the same input property name to multiple outputs. This means that
6
+ Modelish::Base#translations is now a Hash instead of an Array.
7
+ * Bug fix for Modelish::Base#to_hash to return typed property values
8
+
3
9
  ## 0.1.3 (2011-10-25)
4
10
 
5
11
  * Added configuration option to ignore unknown properties. The default behavior continues
data/lib/modelish/base.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  require 'hashie'
2
+ require 'modelish/property_translations'
2
3
  require 'modelish/property_types'
3
4
  require 'modelish/validations'
4
5
  require 'modelish/configuration'
5
6
 
6
7
  module Modelish
7
- class Base < Hashie::Trash
8
+ class Base < Hashie::Dash
9
+ include PropertyTranslations
8
10
  include PropertyTypes
9
11
  include Validations
10
12
  extend Configuration
@@ -36,6 +38,7 @@ module Modelish
36
38
  super
37
39
 
38
40
  add_property_type(name, options[:type]) if options[:type]
41
+ add_property_translation(options[:from], name) if options[:from]
39
42
 
40
43
  add_validator(name) { |val| validate_required(name => val).first } if options[:required]
41
44
  add_validator(name) { |val| validate_length(name, val, options[:max_length]) } if options[:max_length]
@@ -43,6 +46,26 @@ module Modelish
43
46
  add_validator(name) { |val| validate_type(name, val, options[:type]) } if options[:validate_type]
44
47
  end
45
48
 
49
+ # Convert this Modelish object into a vanilla Hash with stringified keys.
50
+ #
51
+ # @return [Hash] the hash of properties
52
+ def to_hash
53
+ out = {}
54
+ self.class.properties.each do |p|
55
+ val = self.send(p)
56
+ out[p.to_s] = val.respond_to?(:to_hash) ? val.to_hash : val
57
+ end
58
+ out
59
+ end
60
+
61
+ def []=(property, value)
62
+ if self.class.translations.keys.include?(property.to_sym)
63
+ send("#{property}=", value)
64
+ elsif property_exists?(property)
65
+ super
66
+ end
67
+ end
68
+
46
69
  private
47
70
  def property_exists?(property)
48
71
  if self.class.property?(property.to_sym)
@@ -0,0 +1,81 @@
1
+ module Modelish
2
+ # Mixin behavior for mapping one property name to another
3
+ module PropertyTranslations
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ # Adds a property translation to the model.
10
+ # This maps a mutator name to an existing property,
11
+ # so that whenever the from_name mutator is called on the
12
+ # model, the to_name property receives the value. If subsequent
13
+ # method calls add more destinations for the same source from_name,
14
+ # all destination properties will be updated.
15
+ #
16
+ # @example A one-to-one property translation
17
+ # class MyClass
18
+ # include Modelish::PropertyTranslations
19
+ #
20
+ # attr_accessor :foo
21
+ #
22
+ # add_property_translation(:camelFoo, :foo)
23
+ # end
24
+ #
25
+ # model = MyClass.new
26
+ # model.camelFoo = 'some value'
27
+ # model.foo
28
+ # # => "some value"
29
+ #
30
+ # @example A one-to-many property translation
31
+ # class
32
+ # include ModelisH::PropertyTranslations
33
+ #
34
+ # attr_accessor :foo, :bar
35
+ #
36
+ # add_property_translation(:source, :foo)
37
+ # add_property_translation(:source, :bar)
38
+ # end
39
+ #
40
+ # model = MyClass.new(:source => 'some value')
41
+ # model.foo
42
+ # # => "some value"
43
+ # model.bar
44
+ # # => "some value"
45
+ #
46
+ # model.foo = 'some other value'
47
+ # model.foo
48
+ # # => "some other value"
49
+ # model.bar
50
+ # # => "some value"
51
+ #
52
+ # model.source = 'new value'
53
+ # model.foo
54
+ # # => "new value"
55
+ # model.bar
56
+ # # => "new value"
57
+ #
58
+ # @param [Symbol,String] from_name the name of the source property
59
+ # @param [Symbol,String] to_name the name of the destination property
60
+ def add_property_translation(from_name, to_name)
61
+ self.translations[from_name.to_sym] ||= []
62
+ self.translations[from_name.to_sym] << to_name.to_sym
63
+
64
+ class_eval do
65
+ define_method("#{from_name}=") do |value|
66
+ self.class.translations[from_name.to_sym].each do |prop|
67
+ self.send("#{prop}=", value)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ # A map of the translations that have already been configured, keyed on from_name.
74
+ #
75
+ # @return [Hash<Symbol,Array>] the key is the from_name, the value is an array to_names
76
+ def translations
77
+ @translations ||= {}
78
+ end
79
+ end
80
+ end
81
+ end
@@ -1,3 +1,3 @@
1
1
  module Modelish
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -47,6 +47,11 @@ describe Modelish::Base do
47
47
  end
48
48
  end
49
49
  end
50
+
51
+ describe "#to_hash" do
52
+ subject { model.to_hash }
53
+ it { should be_empty }
54
+ end
50
55
  end
51
56
 
52
57
  context "with simple property" do
@@ -57,6 +62,15 @@ describe Modelish::Base do
57
62
 
58
63
  it_should_behave_like 'a modelish property'
59
64
  it_should_behave_like 'a valid model'
65
+
66
+ describe "#to_hash" do
67
+ let(:init_options) { {property_name => property_value} }
68
+ subject { model.to_hash }
69
+
70
+ it { should have(1).key_pair }
71
+ it { should have_key(property_name.to_s) }
72
+ its(['simple_property']) { should == property_value }
73
+ end
60
74
  end
61
75
 
62
76
  context "with property default value" do
@@ -68,31 +82,159 @@ describe Modelish::Base do
68
82
 
69
83
  it_should_behave_like 'a modelish property'
70
84
  it_should_behave_like 'a valid model'
85
+
86
+ describe "#to_hash" do
87
+ subject { model.to_hash }
88
+
89
+ context "with default value" do
90
+ it { should have(1).key_pair }
91
+ it { should have_key(property_name.to_s) }
92
+ its(["default_property"]) { should == default_value }
93
+ end
94
+
95
+ context 'without default value' do
96
+ let(:init_options) { {property_name => property_value} }
97
+
98
+ it { should have(1).key_pair }
99
+ it { should have_key(property_name.to_s) }
100
+ its(["default_property"]) { should == property_value }
101
+ end
102
+ end
71
103
  end
72
104
 
73
105
  context "with translated property" do
74
- before { model_class.property(property_name, :from => from_name) }
106
+ before { model_class.property(to_name, :from => from_name) }
75
107
 
76
- subject { model }
77
-
78
- let(:property_name) { :translated_property }
108
+ let(:to_name) { :translated_property }
79
109
  let(:from_name) { 'OldPropertyNAME' }
80
110
  let(:property_value) { 'new value' }
81
111
 
82
- it_should_behave_like 'a modelish property'
112
+ subject { model }
113
+
114
+ context 'when there is one translation for the source property' do
115
+ it_should_behave_like 'a modelish property' do
116
+ let(:property_name) { to_name }
117
+ end
118
+
119
+ it { should_not respond_to(from_name) }
120
+ it { should respond_to("#{from_name}=") }
83
121
 
84
- it { should_not respond_to(from_name) }
85
- it { should respond_to("#{from_name}=") }
122
+ describe "translated mutator" do
123
+ subject { model.send("#{from_name}=", property_value) }
86
124
 
87
- describe "original setter" do
88
- subject { model.send("#{from_name}=", property_value) }
125
+ it "should change the property value" do
126
+ expect { subject }.to change{model.send(to_name)}.from(nil).to(property_value)
127
+ end
128
+ end
129
+
130
+ it_should_behave_like 'a valid model'
131
+
132
+ describe "#to_hash" do
133
+ subject { model.to_hash }
89
134
 
90
- it "should change the property value" do
91
- expect { subject }.to change{model.send(property_name)}.from(nil).to(property_value)
135
+ context "when set from untranslated property name" do
136
+ let(:init_options) { {to_name => property_value} }
137
+
138
+ it { should have(1).key_pair }
139
+ it { should have_key(to_name.to_s) }
140
+ its(['translated_property']) { should == property_value }
141
+ end
142
+
143
+ context "when set from the translation" do
144
+ let(:init_options) { {from_name => property_value} }
145
+
146
+ it { should have(1).key_pair }
147
+ it { should have_key(to_name.to_s) }
148
+ its(['translated_property']) { should == property_value }
149
+ end
92
150
  end
93
151
  end
94
152
 
95
- it_should_behave_like 'a valid model'
153
+ context 'when there are multiple translations for the source property' do
154
+ before { model_class.property(other_to_name, :from => from_name) }
155
+ let(:other_to_name) { :my_other_prop }
156
+
157
+ it_should_behave_like 'a modelish property' do
158
+ let(:property_name) { other_to_name }
159
+ end
160
+
161
+ it_should_behave_like 'a modelish property' do
162
+ let(:property_name) { to_name }
163
+ end
164
+
165
+ it_should_behave_like 'a valid model'
166
+
167
+ it { should_not respond_to(from_name) }
168
+ it { should respond_to("#{from_name}=") }
169
+
170
+ describe "translated mutator" do
171
+ subject { model.send("#{from_name}=", property_value) }
172
+
173
+ it "should change the first property's value" do
174
+ expect { subject }.to change{model.send(to_name)}.from(nil).to(property_value)
175
+ end
176
+
177
+ it "should change the other property's value" do
178
+ expect { subject }.to change { model.send(other_to_name) }.from(nil).to(property_value)
179
+ end
180
+ end
181
+
182
+ describe "#to_hash" do
183
+ subject { model.to_hash }
184
+
185
+ context 'when initialized with first property' do
186
+ let(:init_options) { {to_name => property_value} }
187
+
188
+ it { should have(2).key_pairs }
189
+
190
+ it { should have_key(to_name.to_s) }
191
+ its(['translated_property']) { should == property_value }
192
+
193
+ it { should have_key(other_to_name.to_s) }
194
+ its(['my_other_prop']) { should be_nil }
195
+ end
196
+
197
+ context 'when initialized with second property' do
198
+ let(:init_options) { {other_to_name => property_value} }
199
+
200
+ it { should have(2).key_pairs }
201
+
202
+ it { should have_key(to_name.to_s) }
203
+ its(['translated_property']) { should be_nil }
204
+
205
+ it { should have_key(other_to_name.to_s) }
206
+ its(['my_other_prop']) { should == property_value }
207
+ end
208
+
209
+ context 'when initialized with translated property' do
210
+ let(:init_options) { {from_name => property_value} }
211
+
212
+ context 'when there are no individual property initializations' do
213
+
214
+ it { should have(2).key_pairs }
215
+
216
+ it { should have_key(to_name.to_s) }
217
+ its(['translated_property']) { should == property_value }
218
+
219
+ it { should have_key(other_to_name.to_s) }
220
+ its(['my_other_prop']) { should == property_value }
221
+ end
222
+
223
+ context 'when one of the destination properties is independently initialized' do
224
+ before { init_options[to_name] = other_value }
225
+ let(:other_value) { 'and now for something completely different' }
226
+
227
+ it { should have(2).key_pairs }
228
+
229
+ it { should have_key(to_name.to_s) }
230
+ its(['translated_property']) { should == other_value }
231
+
232
+ it { should have_key(other_to_name.to_s) }
233
+ its(['my_other_prop']) { should == property_value }
234
+ end
235
+ end
236
+ end
237
+ end
96
238
  end
97
239
 
98
240
  context "with typed property" do
@@ -114,6 +256,14 @@ describe Modelish::Base do
114
256
  context "without init options" do
115
257
  it_should_behave_like 'a typed property', :my_int_property, Integer
116
258
  it_should_behave_like 'a valid model'
259
+
260
+ describe "#to_hash" do
261
+ subject { model.to_hash }
262
+
263
+ it { should have(1).key_pair }
264
+ it { should have_key(property_name.to_s) }
265
+ its(['my_int_property']) { should be_nil }
266
+ end
117
267
  end
118
268
 
119
269
  context "with init options" do
@@ -123,6 +273,14 @@ describe Modelish::Base do
123
273
  its(:raw_my_int_property) { should == valid_string }
124
274
 
125
275
  it_should_behave_like 'a valid model'
276
+
277
+ describe "#to_hash" do
278
+ subject { model.to_hash }
279
+
280
+ it { should have(1).key_pair }
281
+ it { should have_key(property_name.to_s) }
282
+ its(['my_int_property']) { should == valid_typed_value }
283
+ end
126
284
  end
127
285
  end
128
286
 
@@ -133,6 +291,14 @@ describe Modelish::Base do
133
291
  it_should_behave_like 'a typed property', :my_int_property, Integer
134
292
 
135
293
  it_should_behave_like 'a valid model'
294
+
295
+ describe "#to_hash" do
296
+ subject { model.to_hash }
297
+
298
+ it { should have(1).key_pair }
299
+ it { should have_key(property_name.to_s) }
300
+ its(['my_int_property']) { should == default_value }
301
+ end
136
302
  end
137
303
  end
138
304
 
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe Modelish::PropertyTranslations do
4
+ let(:model_class) do
5
+ Class.new do
6
+ include Modelish::PropertyTranslations
7
+
8
+ attr_accessor :foo_prop, :bar_prop
9
+ end
10
+ end
11
+
12
+ let(:model) { model_class.new }
13
+
14
+ subject { model_class }
15
+
16
+ describe '.translations' do
17
+ subject { model_class.translations }
18
+
19
+ it { should be_a Hash }
20
+ it { should be_empty }
21
+ end
22
+
23
+ describe '.add_property_translation' do
24
+ subject { add_translation }
25
+ let(:add_translation) { model_class.add_property_translation(from_name, to_name) }
26
+
27
+ context 'when there are no existing translations for the property' do
28
+ context 'with symbolic property names' do
29
+ let(:from_name) { :my_input_prop }
30
+ let(:to_name) { :foo_prop }
31
+
32
+ it 'should map the input property to the output property in the translations hash' do
33
+ expect { subject }.to change { model_class.translations[from_name] }.to([to_name])
34
+ end
35
+
36
+ describe "the generated model" do
37
+ before { add_translation }
38
+
39
+ subject { model }
40
+
41
+ it { should respond_to(to_name) }
42
+ it { should_not respond_to(from_name) }
43
+
44
+ it { should respond_to("#{to_name}=") }
45
+
46
+ let(:value) { 'blah' }
47
+
48
+ describe "non-translated mutator" do
49
+ subject { model.send("#{to_name}=", value) }
50
+
51
+ it 'should change the property value' do
52
+ expect { subject }.to change { model.send(to_name) }.from(nil).to(value)
53
+ end
54
+ end
55
+
56
+ it { should respond_to("#{from_name}=") }
57
+
58
+ describe "translated mutator" do
59
+ subject { model.send("#{from_name}=", value) }
60
+
61
+ it 'should change the property value' do
62
+ expect { subject }.to change { model.send(to_name) }.from(nil).to(value)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'with non-symbolic property names' do
69
+ let(:from_name) { 'my_input_prop' }
70
+ let(:to_name) { 'foo_prop' }
71
+
72
+ it 'should map the symbolic input property to the symbolic output property in the translations hash' do
73
+ expect { subject }.to change { model_class.translations[from_name.to_sym] }.from(nil).to([to_name.to_sym])
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'when there is an existing translation for the property' do
79
+ before { model_class.add_property_translation(from_name, other_to_name) }
80
+ let(:other_to_name) { :bar_prop }
81
+ let(:from_name) { :input_prop }
82
+ let(:to_name) { :foo_prop }
83
+
84
+ it 'should add output property to the mapping for the input property' do
85
+ subject
86
+ model_class.translations[from_name].should include(to_name)
87
+ model_class.translations[from_name].should include(other_to_name)
88
+ end
89
+
90
+ describe 'generated model' do
91
+ before { add_translation }
92
+ subject { model }
93
+
94
+ it { should respond_to(other_to_name) }
95
+ it { should respond_to(to_name) }
96
+ it { should_not respond_to(from_name) }
97
+
98
+ it { should respond_to("#{other_to_name}=") }
99
+
100
+ let(:value) { 'blah' }
101
+
102
+ describe 'non-translated mutator for the existing property' do
103
+ subject { model.send("#{other_to_name}=", value) }
104
+
105
+ it 'should change the value for the existing property' do
106
+ expect { subject }.to change { model.send(other_to_name) }.to(value)
107
+ end
108
+
109
+ it 'should not change the value for the new property' do
110
+ expect { subject }.to_not change { model.send(to_name) }
111
+ end
112
+ end
113
+
114
+ it { should respond_to("#{to_name}=") }
115
+
116
+ describe 'non-translated mutator for the new property' do
117
+ subject { model.send("#{to_name}=", value) }
118
+
119
+ it 'should change the value for the new property' do
120
+ expect { subject }.to change { model.send(to_name) }.to(value)
121
+ end
122
+
123
+ it 'should not change the value for the existing property' do
124
+ expect { subject }.to_not change { model.send(other_to_name) }
125
+ end
126
+ end
127
+
128
+ it { should respond_to("#{from_name}=") }
129
+
130
+ describe 'translated mutator' do
131
+ subject { model.send("#{from_name}=", value) }
132
+
133
+ it 'should change the value for the existing property' do
134
+ expect { subject }.to change { model.send(other_to_name) }.to(value)
135
+ end
136
+
137
+ it 'should change the value for the new property' do
138
+ expect { subject }.to change { model.send(to_name) }.to(value)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modelish
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 3
10
- version: 0.1.3
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Maeve Revels
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-25 00:00:00 -07:00
18
+ date: 2012-01-16 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -101,12 +101,14 @@ files:
101
101
  - lib/modelish.rb
102
102
  - lib/modelish/base.rb
103
103
  - lib/modelish/configuration.rb
104
+ - lib/modelish/property_translations.rb
104
105
  - lib/modelish/property_types.rb
105
106
  - lib/modelish/validations.rb
106
107
  - lib/modelish/version.rb
107
108
  - modelish.gemspec
108
109
  - spec/modelish/base_spec.rb
109
110
  - spec/modelish/configuration_spec.rb
111
+ - spec/modelish/property_translations_spec.rb
110
112
  - spec/modelish/property_types_spec.rb
111
113
  - spec/modelish/validations_spec.rb
112
114
  - spec/modelish_spec.rb
@@ -152,6 +154,7 @@ summary: A lightweight pseudo-modeling not-quite-framework
152
154
  test_files:
153
155
  - spec/modelish/base_spec.rb
154
156
  - spec/modelish/configuration_spec.rb
157
+ - spec/modelish/property_translations_spec.rb
155
158
  - spec/modelish/property_types_spec.rb
156
159
  - spec/modelish/validations_spec.rb
157
160
  - spec/modelish_spec.rb