datamapa 0.0.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e7cce387f3abe430cd8e4ee35617732e25e92e39
4
+ data.tar.gz: 0d6378a3d032543f8d498e25fb403ed3107b36be
5
+ SHA512:
6
+ metadata.gz: abbc9b71aeb881fd50b48db2ff7de09d45426f4f65d968c34ab1e7cd9955c2515cbbd86125a0352161b4be6fab791b8cb65666a74f5575da7595f1b8258f0498
7
+ data.tar.gz: f352af17f79fb6bc50571ad3ce023c8e7d76b968b4961bac62972f8799e0cbb66ea9bead7396a6b4ac20b4a7e738e4d0d21a3d9acb446d2b1108c00e714e5d5d
data/datamapa.gemspec CHANGED
@@ -18,6 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency 'minitest'
22
- spec.add_development_dependency 'mocha'
21
+ spec.add_runtime_dependency 'activerecord', '~> 4.0.2'
22
+
23
+ spec.add_development_dependency 'rake', '~> 10.1.1'
24
+ spec.add_development_dependency 'minitest', '~> 4.2'
25
+ spec.add_development_dependency 'mocha', '~> 0.14.0'
26
+ spec.add_development_dependency 'bourne', '~> 1.5.0'
27
+ spec.add_development_dependency 'byebug', '~> 2.6.0'
23
28
  end
@@ -1,3 +1,3 @@
1
1
  module DataMapa
2
- VERSION = "0.0.5"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/datamapa.rb CHANGED
@@ -1,4 +1,5 @@
1
- require "datamapa/version"
1
+ require 'datamapa/version'
2
+ require 'active_record'
2
3
 
3
4
  module DataMapa
4
5
  def self.included(base)
@@ -6,13 +7,19 @@ module DataMapa
6
7
  end
7
8
 
8
9
  module ClassMethods
10
+
9
11
  # Declarative methods
12
+
10
13
  def active_record_class(klass)
11
14
  @ar_class = klass
12
15
  end
13
16
 
14
- def model_constructor(method)
15
- @model_constructor = method
17
+ def creates_model_with(&block)
18
+ @create_model_proc = block
19
+ end
20
+
21
+ def semantic_key(key)
22
+ @semantic_key = key
16
23
  end
17
24
 
18
25
  def simple_attr(attributes)
@@ -23,52 +30,153 @@ module DataMapa
23
30
  @ref_attr = attributes
24
31
  end
25
32
 
26
- def collection_attr(attributes)
27
- @collection_attr = attributes
33
+ def aggregates(components)
34
+ @aggregates = components
28
35
  end
29
36
 
30
- # Public methods
31
- def to_ar(model, options={})
32
- ar = model.id ? @ar_class.find(model.id) : @ar_class.new
33
-
34
- o2r_attr(model, ar)
35
- o2r_ref(model, ar)
36
- o2r_collection(model, ar, options[:include]) if options[:include]
37
-
38
- ar
37
+ def composed_of(parts)
38
+ @composed_of = parts
39
39
  end
40
40
 
41
- def to_model(ar, options={})
42
- model = @model_constructor.call
43
-
44
- r2o_simple(ar, model)
45
- r2o_ref(ar, model)
46
- r2o_collection(ar, model, options[:include]) if options[:include]
47
-
48
- model
41
+ def composes(parent)
42
+ @composes = parent
49
43
  end
50
44
 
45
+ # Public methods
46
+
51
47
  def find!(id)
52
48
  begin
53
- relational = @ar_class.find(id)
54
- to_model(relational)
49
+ ar = @ar_class.find(id)
50
+ model_for(ar)
55
51
  rescue ActiveRecord::RecordNotFound
56
52
  raise DataMapa::RecordNotFoundError
57
53
  end
58
54
  end
59
55
 
60
- def save!(model)
56
+ def where(clause)
57
+ records = @ar_class.where(clause)
58
+ records.map do |ar|
59
+ model_for(ar)
60
+ end
61
+ end
62
+
63
+ def create!(model, extras={})
61
64
  begin
62
- ar = to_ar(model)
63
- ar.save!
65
+ attributes = attribute_hash(model, extras)
66
+
67
+ ar = @ar_class.create!(attributes)
64
68
  model.send(:id=, ar.id)
65
- rescue ActiveRecord::StatementInvalid
66
- raise DataMapa::DuplicateKeyError
69
+
70
+ @composed_of.each do |parts, mapper|
71
+ mapper.create_parts!(model.send(parts), model.id)
72
+ end if @composed_of
73
+ rescue ActiveRecord::StatementInvalid => e
74
+ raise DataMapa::PersistenceError, e.message
67
75
  end
68
76
  end
69
77
 
78
+ def update(model, extras={})
79
+ begin
80
+ attributes = attribute_hash(model, extras)
81
+
82
+ @ar_class.update(model.id, attributes)
83
+
84
+ @composed_of.each do |parts, mapper|
85
+ mapper.update_parts!(model.send(parts), model.id)
86
+ end if @composed_of
87
+ rescue ActiveRecord::StatementInvalid => e
88
+ raise DataMapa::PersistenceError, e.message
89
+ end
90
+ end
91
+
92
+ def save!(model, extras={})
93
+ load_id_with_semantic_key(model) unless @semantic_key.nil?
94
+ if model.id.nil?
95
+ create!(model)
96
+ else
97
+ update(model)
98
+ end
99
+ end
100
+
101
+ def delete!(id)
102
+ @composed_of.each do |part, mapper|
103
+ mapper.delete_children_of(id)
104
+ end
105
+
106
+ count = @ar_class.delete(id)
107
+ end
108
+
109
+ def model_for(ar)
110
+ model = @create_model_proc.call(ar)
111
+ r2o(ar, model)
112
+ model.id = ar.id
113
+ model
114
+ end
115
+
116
+ protected
117
+
118
+ def create_parts!(parts, parent_id)
119
+ parts.each_with_index do |item, i|
120
+ create!(item, "#{@composes}_id".to_sym => parent_id, :index => i)
121
+ end
122
+ end
123
+
124
+ def update_parts!(parts, parent_id)
125
+ existing_ids = parts.map(&:id).reject { |id| id.nil? }
126
+ @ar_class.where(composes_column => parent_id).where.not(id: existing_ids).delete_all
127
+
128
+ parts.each_with_index do |item, i|
129
+ if item.id.nil?
130
+ create!(item, composes_column => parent_id, :index => i)
131
+ else
132
+ update(item, composes_column => parent_id, :index => i)
133
+ end
134
+ end
135
+ end
136
+
137
+ def delete_children_of(id)
138
+ @ar_class.delete_all(["#{@composes}_id = ?", id])
139
+ end
140
+
70
141
  private
71
142
 
143
+ def attribute_hash(model, extras)
144
+ attributes = {}
145
+
146
+ @simple_attr.each do |attr|
147
+ attributes[:"#{attr.to_s.chomp('?')}"] = model.send(attr)
148
+ end if @simple_attr
149
+
150
+ @ref_attr.each_key do |attr|
151
+ ref = model.send(attr)
152
+ attributes[:"#{attr.to_s.chomp('?')}_id"] = ref.id unless ref.nil?
153
+ end if @ref_attr
154
+
155
+ extras.each_pair { |key, value| attributes[:"#{key}"] = value }
156
+
157
+ attributes
158
+ end
159
+
160
+ def load_id_with_semantic_key(model)
161
+ clause = @semantic_key.inject({}) do |memo, attr|
162
+ memo[attr] = model.send(attr)
163
+ memo
164
+ end
165
+ ar = @ar_class.find_by(clause)
166
+ model.id = ar.id unless ar.nil?
167
+ end
168
+
169
+ def composes_column
170
+ "#{@composes}_id".to_sym
171
+ end
172
+
173
+ def r2o(ar, model)
174
+ r2o_simple(ar, model)
175
+ r2o_ref(ar, model)
176
+ r2o_collection(ar, model, @composed_of) if @composed_of
177
+ model
178
+ end
179
+
72
180
  def r2o_simple(relational, object)
73
181
  @simple_attr.each do |attr|
74
182
  setter = "#{attr.to_s.chomp('?')}="
@@ -76,47 +184,37 @@ module DataMapa
76
184
  end if @simple_attr
77
185
  end
78
186
 
79
- def r2o_ref(relational, object)
187
+ def r2o_ref(ar, object)
80
188
  @ref_attr.each do |attr, mapper|
189
+ model = mapper.find!(ar.send("#{attr}_id"))
190
+
81
191
  setter = "#{attr}="
82
- object.send(setter, mapper.to_model(relational.send(attr)))
192
+ object.send(setter, model)
83
193
  end if @ref_attr
84
194
  end
85
195
 
196
+ def where_clause_for_references_to(id)
197
+ {"#{model_name}_id".to_sym => id}
198
+ end
199
+
86
200
  def r2o_collection(ar, model, attributes)
87
- attributes.each do |attr|
88
- ar_items = ar.send(attr)
89
- model_items = ar_items.map {|i| @collection_attr[attr].to_model(i)}
201
+ attributes.each do |attr, mapper|
202
+ model_items = mapper.where(where_clause_for_references_to(ar.id))
90
203
  model.send("#{attr}=", model_items)
91
204
  end
92
205
  end
93
206
 
94
- def o2r_attr(object, relational)
95
- @simple_attr.each do |attr|
96
- relational.send("#{attr.to_s.chomp('?')}=", object.send(attr))
97
- end if @simple_attr
98
- end
99
-
100
- def o2r_ref(object, relational)
101
- @ref_attr.each_key do |attr|
102
- ref = object.send(attr)
103
- relational.send("#{attr.to_s.chomp('?')}_id=", ref.id) unless ref.nil?
104
- end if @ref_attr
207
+ def model_name
208
+ name.chomp('Mapper').downcase
105
209
  end
210
+ end
106
211
 
107
- def o2r_collection(object, relational, attributes)
108
- attributes.each do |attr|
109
- collection = object.send(attr).map do |item|
110
- @collection_attr[attr].to_ar(item)
111
- end
112
- relational.send("#{attr}=", collection)
113
- end
114
- end
212
+ class PersistenceError < StandardError
115
213
  end
116
214
 
117
- class RecordNotFoundError < StandardError
215
+ class RecordNotFoundError < PersistenceError
118
216
  end
119
217
 
120
- class DuplicateKeyError < StandardError
218
+ class DuplicateKeyError < PersistenceError
121
219
  end
122
220
  end
@@ -2,181 +2,403 @@ require 'rubygems'
2
2
  gem 'minitest'
3
3
  require 'minitest/autorun'
4
4
  require 'mocha/setup'
5
+ require 'bourne'
5
6
  require 'datamapa'
6
7
 
7
8
  describe DataMapa do
8
9
 
10
+ class MapperStub
11
+ end
12
+
13
+ def ar_class_with_attributes(attributes)
14
+ Class.new do
15
+ attributes.each do |attr|
16
+ attr_accessor attr
17
+ end
18
+
19
+ def initialize(id=nil)
20
+ @id = id
21
+ end
22
+
23
+ def self.update(id, hash)
24
+ end
25
+ end
26
+ end
27
+
9
28
  def class_with_attributes(attributes)
10
29
  Class.new do
11
30
  attributes.each do |attr|
12
31
  attr_accessor attr
13
32
  end
33
+
34
+ def initialize(id=nil)
35
+ @id = id
36
+ end
14
37
  end
15
38
  end
16
39
 
17
- def mapper_class(ar_class, model_class, attributes)
40
+ def mapper_class(class_name, attributes)
18
41
  Class.new do
19
42
  include DataMapa
20
43
 
21
- active_record_class ar_class
22
- model_constructor model_class.method(:new)
23
- simple_attr attributes[:simple] if attributes[:simple]
24
- ref_attr attributes[:ref] if attributes[:ref]
25
- collection_attr attributes[:collection] if attributes[:collection]
44
+ active_record_class attributes[:active_record_class]
45
+ creates_model_with &attributes[:creates_model_with]
46
+ simple_attr attributes[:simple_attr] if attributes[:simple_attr]
47
+ ref_attr attributes[:ref_attr] if attributes[:ref_attr]
48
+ semantic_key attributes[:semantic_key] if attributes[:semantic_key]
49
+ composed_of attributes[:composed_of] if attributes[:composed_of]
50
+ composes attributes[:composes] if attributes[:composes]
51
+ aggregates attributes[:aggregates] if attributes[:aggregates]
52
+
53
+ # Provide name because this mapper class is anonymous
54
+ define_singleton_method :name do
55
+ class_name
56
+ end
26
57
  end
27
58
  end
28
59
 
60
+ let (:any_id) { 1 }
29
61
  let (:any_object) { Object.new }
30
62
 
31
63
  describe "simple attribute" do
32
- let(:ar_class) { class_with_attributes([:id, :a1]) }
33
- let(:model_class) { class_with_attributes([:id, :a1]) }
34
- let(:mapper) { mapper_class(
35
- ar_class, model_class,
36
- simple: [:id, :a1]
37
- ) }
64
+ let(:ar_class) { ar_class_with_attributes([:id, :attribute]) }
65
+ let(:model_class) { class_with_attributes([:id, :attribute]) }
66
+ let(:mapper) do
67
+ mapper_class(
68
+ 'SimpleMapper',
69
+ active_record_class: ar_class,
70
+ creates_model_with: lambda { |rec| model_class.new },
71
+ simple_attr: [:attribute]
72
+ )
73
+ end
38
74
 
39
- it "converts to model" do
40
- ar = stub(id: 1, a1: 'any string')
75
+ it "finds model" do
76
+ id = any_id
77
+ result_ar = ar_class.new(id)
78
+ result_ar.attribute = 'any string'
41
79
 
42
- model = mapper.to_model(ar)
80
+ ar_class.stubs(:find).with(id).returns(result_ar)
43
81
 
44
- model.id.must_equal ar.id
45
- model.a1.must_equal ar.a1
82
+ model = mapper.find!(id)
83
+
84
+ model.id.must_equal result_ar.id
85
+ model.attribute.must_equal result_ar.attribute
46
86
  end
47
87
 
48
- it "converts to ar" do
49
- model = stub(id: nil, a1: 'any string')
88
+ it "creates object" do
89
+ model = model_class.new
90
+ model.attribute = 'any string'
50
91
 
51
- ar = mapper.to_ar(model)
92
+ id = any_id
93
+ ar = ar_class.new(id)
94
+ ar_class.expects(:create!).with(attribute: model.attribute).returns(ar)
52
95
 
53
- ar.id.must_equal model.id
54
- ar.a1.must_equal model.a1
96
+ mapper.create!(model)
97
+
98
+ model.id.must_equal id
55
99
  end
56
- end
57
100
 
58
- describe "ref attribute" do
59
- let(:ar_class) { class_with_attributes([:id, :a1_id]) }
60
- let(:model_class) { class_with_attributes([:id, :a1]) }
61
- let(:a1_model) { any_object }
62
- let(:mapper) { mapper_class(
63
- ar_class, model_class,
64
- simple: [:id],
65
- ref: { a1: stub(to_model: a1_model) }
66
- ) }
67
-
68
- it "converts to model without option" do
69
- ar = stub(id: 1, a1: nil)
101
+ it "updates object" do
102
+ id = any_id
103
+ model = model_class.new(id)
104
+ model.attribute = 'any string'
70
105
 
71
- model = mapper.to_model(ar)
106
+ ar_class.expects(:update).with(id, attribute: model.attribute)
72
107
 
73
- model.id.must_equal ar.id
74
- model.a1.must_equal a1_model
108
+ mapper.update(model)
75
109
  end
76
110
 
77
- it "converts to AR" do
78
- model = stub(id: nil, a1: stub(id: 10))
111
+ it "saves new object" do
112
+ model = model_class.new
113
+ model.attribute = 'any string'
79
114
 
80
- ar = mapper.to_ar(model)
115
+ id = any_id
116
+ ar = ar_class.new(id)
117
+ ar_class.expects(:create!).with(attribute: model.attribute).returns(ar)
81
118
 
82
- ar.id.must_equal model.id
83
- ar.a1_id.must_equal model.a1.id
119
+ mapper.save!(model)
120
+
121
+ model.id.must_equal id
122
+ end
123
+
124
+ it "saves existing object" do
125
+ model = model_class.new(any_id)
126
+ model.attribute = 'any string'
127
+
128
+ ar_class.expects(:update).with(model.id, attribute: model.attribute)
129
+
130
+ mapper.save!(model)
84
131
  end
85
132
  end
86
133
 
87
- describe "collection attribute" do
88
- let(:ar_class) { class_with_attributes([:id, :a1]) }
89
- let(:model_class) { class_with_attributes([:id, :a1]) }
90
- let(:a1_model) { any_object }
91
- let(:a1_ar) { any_object }
92
- let(:mapper) { mapper_class(
93
- ar_class, model_class,
94
- simple: [:id],
95
- collection: {a1: stub(to_model: a1_model, to_ar: a1_ar)}
96
- ) }
134
+ describe "ref attribute" do
135
+ let(:model_class) { class_with_attributes([:id, :object ]) }
136
+ let(:ar_class) { ar_class_with_attributes([:id, :object_id]) }
137
+ let(:ref_mapper) { MapperStub }
138
+ let(:mapper) do
139
+ mapper_class(
140
+ 'RefMapper',
141
+ active_record_class: ar_class,
142
+ creates_model_with: lambda { |rec| model_class.new },
143
+ ref_attr: { object: ref_mapper }
144
+ )
145
+ end
97
146
 
98
- it "converts to model with option" do
99
- ar = stub(id: 1, a1: [Object.new, Object.new])
147
+ it "creates model from ar" do
148
+ object = any_object
149
+ ref_mapper.stubs(:find!).returns(object)
150
+
151
+ ar = ar_class.new(any_id)
100
152
 
101
- model = mapper.to_model(ar, include: [:a1])
153
+ model = mapper.model_for(ar)
102
154
 
103
155
  model.id.must_equal ar.id
104
- model.a1[0].must_equal a1_model
105
- model.a1[1].must_equal a1_model
156
+ model.object.must_equal object
106
157
  end
107
158
 
108
- it "does not convert to model without option" do
109
- ar = stub(id: 1, a1: [Object.new, Object.new])
159
+ it "maps when creating object" do
160
+ model = model_class.new
161
+ model.object = stub(id: 10)
110
162
 
111
- model = mapper.to_model(ar)
163
+ id = any_id
164
+ ar = ar_class.new(id)
112
165
 
113
- model.id.must_equal ar.id
114
- model.a1.must_equal nil
166
+ ar_class.expects(:create!).with(object_id: model.object.id).returns(ar)
167
+
168
+ mapper.create!(model)
169
+
170
+ model.id.must_equal id
115
171
  end
172
+ end
173
+
174
+ describe "aggregation" do
175
+ let(:ar_class) { ar_class_with_attributes([:id, :components]) }
176
+ let(:model_class) { class_with_attributes([:id, :components]) }
177
+ let(:mapper) do
178
+ mapper_class(
179
+ 'AggregateMapper',
180
+ active_record_class: ar_class,
181
+ creates_model_with: lambda { |rec| model_class.new },
182
+ aggregates: { components: MapperStub }
183
+ )
184
+ end
116
185
 
117
- it "converts to AR with option" do
118
- model = stub(id: nil, a1: [Object.new, Object.new])
186
+ it "instantiates model without components" do
187
+ ar = ar_class.new(any_id)
188
+ ar.components = [any_object]
119
189
 
120
- ar = mapper.to_ar(model, include: [:a1])
190
+ model = mapper.model_for(ar)
121
191
 
122
- ar.id.must_equal model.id
123
- ar.a1[0].must_equal a1_ar
124
- ar.a1[1].must_equal a1_ar
192
+ model.id.must_equal ar.id
193
+ model.components.must_equal nil
125
194
  end
126
195
 
127
- it "does not convert to AR without option" do
128
- model = stub(id: nil, a1: [Object.new, Object.new])
196
+ it "persists new model without components" do
197
+ model = model_class.new
198
+ model.components = [any_object]
199
+
200
+ id = any_id
201
+ ar = ar_class.new(id)
202
+ ar_class.expects(:create!).with({}).returns(ar)
129
203
 
130
- ar = mapper.to_ar(model)
204
+ mapper.create!(model)
131
205
 
132
- ar.id.must_equal model.id
133
- ar.a1.must_equal nil
206
+ model.id.must_equal id
134
207
  end
135
208
  end
136
209
 
137
210
  describe "attribute in AR only" do
138
- let(:ar_class) { class_with_attributes([:id, :a1]) }
211
+ let(:ar_class) { ar_class_with_attributes([:id, :attribute]) }
139
212
  let(:model_class) { class_with_attributes([:id]) }
140
- let(:mapper) { mapper_class(ar_class, model_class, simple: [:id]) }
213
+ let(:mapper) do
214
+ mapper_class(
215
+ 'AttributeMapper',
216
+ active_record_class: ar_class,
217
+ creates_model_with: lambda { |rec| model_class.new },
218
+ )
219
+ end
141
220
 
142
- it "converts to model" do
143
- ar = stub(id: 1, a1: 'any string')
221
+ it "instantiates model" do
222
+ ar = ar_class.new(any_id)
223
+ ar.attribute = 'any string'
144
224
 
145
- model = mapper.to_model(ar)
225
+ model = mapper.model_for(ar)
146
226
 
147
227
  model.id.must_equal ar.id
148
228
  end
149
229
 
150
- it "converts to AR" do
151
- model = stub(id: nil)
230
+ it "creates model" do
231
+ model = model_class.new
152
232
 
153
- ar = mapper.to_ar(model)
233
+ id = any_id
234
+ ar = ar_class.new(id)
235
+ ar_class.expects(:create!).with({}).returns(ar)
154
236
 
155
- ar.id.must_equal model.id
156
- ar.a1.must_equal nil
237
+ mapper.create!(model)
238
+
239
+ model.id.must_equal id
157
240
  end
158
241
  end
159
242
 
160
243
  describe "attribute in model only" do
161
- let(:ar_class) { class_with_attributes([:id]) }
162
- let(:model_class) { class_with_attributes([:id, :a1]) }
163
- let(:mapper) { mapper_class(ar_class, model_class, simple: [:id]) }
244
+ let(:ar_class) { ar_class_with_attributes([:id]) }
245
+ let(:model_class) { class_with_attributes([:id, :attribute]) }
246
+ let(:mapper) do
247
+ mapper_class(
248
+ 'AttributeMapper',
249
+ active_record_class: ar_class,
250
+ creates_model_with: lambda { |rec| model_class.new },
251
+ )
252
+ end
164
253
 
165
- it "converts to model" do
166
- ar = stub(id: 1)
254
+ it "instantiates model" do
255
+ ar = ar_class.new(any_id)
167
256
 
168
- model = mapper.to_model(ar)
257
+ model = mapper.model_for(ar)
169
258
 
170
259
  model.id.must_equal ar.id
171
- model.a1.must_equal nil
260
+ model.attribute.must_equal nil
172
261
  end
173
262
 
174
- it "converts to AR" do
175
- model = stub(id: nil, a1: 'any string')
263
+ it "creates model" do
264
+ model = model_class.new
265
+ model.attribute = 'any string'
266
+
267
+ id = any_id
268
+ ar = ar_class.new(id)
269
+ ar_class.expects(:create!).with({}).returns(ar)
270
+
271
+ mapper.create!(model)
272
+
273
+ model.id.must_equal id
274
+ end
275
+ end
276
+
277
+ describe "semantic keys" do
278
+ let(:ar_class) { ar_class_with_attributes([:id, :key1, :key2, :field]) }
279
+ let(:model_class) { class_with_attributes([:id, :key1, :key2, :field]) }
280
+ let(:mapper) do
281
+ mapper_class(
282
+ 'SemanticMapper',
283
+ active_record_class: ar_class,
284
+ creates_model_with: lambda { |ar| model_class.new },
285
+ simple_attr: [:key1, :key2, :field],
286
+ semantic_key: [:key1, :key2]
287
+ )
288
+ end
289
+
290
+ it "saves new object" do
291
+ model = model_class.new
292
+ model.key1 = 10
293
+ model.key2 = 20
294
+ model.field = 'any string'
295
+
296
+ ar_class.stubs(:find_by)
297
+
298
+ id = any_id
299
+ ar = ar_class.new(id)
300
+ ar_class.expects(:create!).with(key1: model.key1, key2: model.key2, field: model.field).returns(ar)
301
+
302
+ mapper.save!(model)
303
+
304
+ model.id.must_equal id
305
+ end
306
+
307
+ it "saves existing object" do
308
+ model = model_class.new
309
+ model.key1 = 10
310
+ model.key2 = 20
311
+ model.field = 'any string'
312
+
313
+ id = any_id
314
+ ar = ar_class.new(id)
315
+
316
+ ar_class.stubs(:find_by).with(key1: model.key1, key2: model.key2).returns(ar)
317
+ ar_class.expects(:update).with(id, key1: model.key1, key2: model.key2, field: model.field)
318
+
319
+ mapper.save!(model)
320
+
321
+ model.id.must_equal id
322
+ end
323
+
324
+ end
325
+
326
+ describe "composition" do
327
+ let(:parts_ar_class) { ar_class_with_attributes([:id, :simple]) }
328
+ let(:parts_model_class) { ar_class_with_attributes([:id, :simple]) }
329
+ let(:parts_mapper) do
330
+ mapper_class(
331
+ 'PartsMapper',
332
+ active_record_class: parts_ar_class,
333
+ simple_attr: [:simple],
334
+ composes: 'composite'
335
+ )
336
+ end
337
+
338
+ let(:ar_class) { ar_class_with_attributes([:id, :parts]) }
339
+ let(:model_class) { class_with_attributes([:id, :parts]) }
340
+ let(:mapper) do
341
+ mapper_class(
342
+ 'CompositeMapper',
343
+ active_record_class: ar_class,
344
+ creates_model_with: lambda { |ar| model_class.new },
345
+ composed_of: { parts: parts_mapper }
346
+ )
347
+ end
348
+
349
+ it "instantiates model with parts" do
350
+ id = any_id
351
+ part = any_object
352
+
353
+ ar = ar_class.new(id)
354
+
355
+ parts_mapper.stubs(:where).with(composite_id: id).returns([part])
356
+
357
+ model = mapper.model_for(ar)
358
+
359
+ model.id.must_equal id
360
+ model.parts.must_equal [part]
361
+ end
362
+
363
+ it "creates parts when creating object" do
364
+ part = parts_ar_class.new
365
+ part.simple = 'any string'
366
+
367
+ model = model_class.new
368
+ model.parts = [part]
369
+
370
+ id = any_id
371
+ ar = ar_class.new(id)
372
+ ar_class.stubs(:create!).returns(ar)
373
+
374
+ parts_ar = parts_ar_class.new(any_id)
375
+ parts_ar_class.expects(:create!).with(simple: part.simple, composite_id: id, index: 0).returns(parts_ar)
376
+
377
+ mapper.create!(model)
378
+ end
379
+
380
+ it "updates parts when updating object" do
381
+ part1 = parts_model_class.new(10)
382
+ part1.simple = 'existing part'
383
+
384
+ part2 = parts_model_class.new
385
+ part2.simple = 'new part'
386
+
387
+ id = any_id
388
+ model = model_class.new(id)
389
+ model.parts = [part1, part2]
390
+
391
+ parts_ar_class.expects(:where).with(composite_id: id).returns(parts_ar_class)
392
+ parts_ar_class.expects(:where).with().returns(parts_ar_class)
393
+ parts_ar_class.expects(:not).with(id: [part1.id]).returns(parts_ar_class)
394
+ parts_ar_class.expects(:delete_all)
395
+
396
+ parts_ar_class.expects(:update).with(part1.id, simple: part1.simple, composite_id: id, index: 0)
176
397
 
177
- ar = mapper.to_ar(model)
398
+ ar = parts_ar_class.new(id)
399
+ parts_ar_class.expects(:create!).with(simple: part2.simple, composite_id: id, index: 1).returns(ar)
178
400
 
179
- ar.id.must_equal model.id
401
+ mapper.update(model)
180
402
  end
181
403
  end
182
404
  end
metadata CHANGED
@@ -1,58 +1,106 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: datamapa
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 0
8
- - 5
9
- version: 0.0.5
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
10
5
  platform: ruby
11
- authors:
6
+ authors:
12
7
  - Yosuke Doi
13
8
  autorequire:
14
9
  bindir: bin
15
10
  cert_chain: []
16
-
17
- date: 2013-06-10 00:00:00 +09:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: minitest
11
+ date: 2014-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.2
20
+ type: :runtime
22
21
  prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- version: "0"
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 10.1.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 10.1.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
31
48
  type: :development
32
- version_requirements: *id001
33
- - !ruby/object:Gem::Dependency
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
34
56
  name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.0
62
+ type: :development
35
63
  prerelease: false
36
- requirement: &id002 !ruby/object:Gem::Requirement
37
- none: false
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 0
43
- version: "0"
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.14.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: bourne
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 1.5.0
44
76
  type: :development
45
- version_requirements: *id002
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 1.5.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 2.6.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 2.6.0
46
97
  description: A minimalistic Data Mapper for removing model dependency on Active Record
47
- email:
98
+ email:
48
99
  - doinchi@gmail.com
49
100
  executables: []
50
-
51
101
  extensions: []
52
-
53
102
  extra_rdoc_files: []
54
-
55
- files:
103
+ files:
56
104
  - .gitignore
57
105
  - Gemfile
58
106
  - LICENSE.txt
@@ -62,37 +110,29 @@ files:
62
110
  - lib/datamapa.rb
63
111
  - lib/datamapa/version.rb
64
112
  - spec/datamapa/datamapa_spec.rb
65
- has_rdoc: true
66
113
  homepage: https://github.com/doiyo/datamapa
67
- licenses:
114
+ licenses:
68
115
  - MIT
116
+ metadata: {}
69
117
  post_install_message:
70
118
  rdoc_options: []
71
-
72
- require_paths:
119
+ require_paths:
73
120
  - lib
74
- required_ruby_version: !ruby/object:Gem::Requirement
75
- none: false
76
- requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- segments:
80
- - 0
81
- version: "0"
82
- required_rubygems_version: !ruby/object:Gem::Requirement
83
- none: false
84
- requirements:
85
- - - ">="
86
- - !ruby/object:Gem::Version
87
- segments:
88
- - 0
89
- version: "0"
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
90
131
  requirements: []
91
-
92
132
  rubyforge_project:
93
- rubygems_version: 1.3.7
133
+ rubygems_version: 2.0.6
94
134
  signing_key:
95
- specification_version: 3
135
+ specification_version: 4
96
136
  summary: Data Mapper using Active Record
97
- test_files:
137
+ test_files:
98
138
  - spec/datamapa/datamapa_spec.rb