datamapa 0.0.5 → 1.0.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.
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