modelish 0.1.3 → 0.2.0

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