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 +7 -0
- data/datamapa.gemspec +7 -2
- data/lib/datamapa/version.rb +1 -1
- data/lib/datamapa.rb +153 -55
- data/spec/datamapa/datamapa_spec.rb +317 -95
- metadata +103 -63
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.
|
22
|
-
|
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
|
data/lib/datamapa/version.rb
CHANGED
data/lib/datamapa.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require
|
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
|
15
|
-
@
|
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
|
27
|
-
@
|
33
|
+
def aggregates(components)
|
34
|
+
@aggregates = components
|
28
35
|
end
|
29
36
|
|
30
|
-
|
31
|
-
|
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
|
42
|
-
|
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
|
-
|
54
|
-
|
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
|
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
|
-
|
63
|
-
|
65
|
+
attributes = attribute_hash(model, extras)
|
66
|
+
|
67
|
+
ar = @ar_class.create!(attributes)
|
64
68
|
model.send(:id=, ar.id)
|
65
|
-
|
66
|
-
|
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(
|
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,
|
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
|
-
|
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
|
95
|
-
|
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
|
-
|
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 <
|
215
|
+
class RecordNotFoundError < PersistenceError
|
118
216
|
end
|
119
217
|
|
120
|
-
class DuplicateKeyError <
|
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(
|
40
|
+
def mapper_class(class_name, attributes)
|
18
41
|
Class.new do
|
19
42
|
include DataMapa
|
20
43
|
|
21
|
-
active_record_class
|
22
|
-
|
23
|
-
simple_attr attributes[:
|
24
|
-
ref_attr attributes[:
|
25
|
-
|
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) {
|
33
|
-
let(:model_class) { class_with_attributes([:id, :
|
34
|
-
let(:mapper)
|
35
|
-
|
36
|
-
|
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 "
|
40
|
-
|
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
|
-
|
80
|
+
ar_class.stubs(:find).with(id).returns(result_ar)
|
43
81
|
|
44
|
-
model
|
45
|
-
|
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 "
|
49
|
-
model =
|
88
|
+
it "creates object" do
|
89
|
+
model = model_class.new
|
90
|
+
model.attribute = 'any string'
|
50
91
|
|
51
|
-
|
92
|
+
id = any_id
|
93
|
+
ar = ar_class.new(id)
|
94
|
+
ar_class.expects(:create!).with(attribute: model.attribute).returns(ar)
|
52
95
|
|
53
|
-
|
54
|
-
|
96
|
+
mapper.create!(model)
|
97
|
+
|
98
|
+
model.id.must_equal id
|
55
99
|
end
|
56
|
-
end
|
57
100
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
106
|
+
ar_class.expects(:update).with(id, attribute: model.attribute)
|
72
107
|
|
73
|
-
model
|
74
|
-
model.a1.must_equal a1_model
|
108
|
+
mapper.update(model)
|
75
109
|
end
|
76
110
|
|
77
|
-
it "
|
78
|
-
model =
|
111
|
+
it "saves new object" do
|
112
|
+
model = model_class.new
|
113
|
+
model.attribute = 'any string'
|
79
114
|
|
80
|
-
|
115
|
+
id = any_id
|
116
|
+
ar = ar_class.new(id)
|
117
|
+
ar_class.expects(:create!).with(attribute: model.attribute).returns(ar)
|
81
118
|
|
82
|
-
|
83
|
-
|
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 "
|
88
|
-
let(:
|
89
|
-
let(:
|
90
|
-
let(:
|
91
|
-
let(:
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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 "
|
99
|
-
|
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.
|
153
|
+
model = mapper.model_for(ar)
|
102
154
|
|
103
155
|
model.id.must_equal ar.id
|
104
|
-
model.
|
105
|
-
model.a1[1].must_equal a1_model
|
156
|
+
model.object.must_equal object
|
106
157
|
end
|
107
158
|
|
108
|
-
it "
|
109
|
-
|
159
|
+
it "maps when creating object" do
|
160
|
+
model = model_class.new
|
161
|
+
model.object = stub(id: 10)
|
110
162
|
|
111
|
-
|
163
|
+
id = any_id
|
164
|
+
ar = ar_class.new(id)
|
112
165
|
|
113
|
-
model.id.
|
114
|
-
|
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 "
|
118
|
-
|
186
|
+
it "instantiates model without components" do
|
187
|
+
ar = ar_class.new(any_id)
|
188
|
+
ar.components = [any_object]
|
119
189
|
|
120
|
-
|
190
|
+
model = mapper.model_for(ar)
|
121
191
|
|
122
|
-
|
123
|
-
|
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 "
|
128
|
-
model =
|
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
|
-
|
204
|
+
mapper.create!(model)
|
131
205
|
|
132
|
-
|
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) {
|
211
|
+
let(:ar_class) { ar_class_with_attributes([:id, :attribute]) }
|
139
212
|
let(:model_class) { class_with_attributes([:id]) }
|
140
|
-
let(:mapper)
|
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 "
|
143
|
-
ar =
|
221
|
+
it "instantiates model" do
|
222
|
+
ar = ar_class.new(any_id)
|
223
|
+
ar.attribute = 'any string'
|
144
224
|
|
145
|
-
model = mapper.
|
225
|
+
model = mapper.model_for(ar)
|
146
226
|
|
147
227
|
model.id.must_equal ar.id
|
148
228
|
end
|
149
229
|
|
150
|
-
it "
|
151
|
-
model =
|
230
|
+
it "creates model" do
|
231
|
+
model = model_class.new
|
152
232
|
|
153
|
-
|
233
|
+
id = any_id
|
234
|
+
ar = ar_class.new(id)
|
235
|
+
ar_class.expects(:create!).with({}).returns(ar)
|
154
236
|
|
155
|
-
|
156
|
-
|
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) {
|
162
|
-
let(:model_class) { class_with_attributes([:id, :
|
163
|
-
let(:mapper)
|
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 "
|
166
|
-
ar =
|
254
|
+
it "instantiates model" do
|
255
|
+
ar = ar_class.new(any_id)
|
167
256
|
|
168
|
-
model = mapper.
|
257
|
+
model = mapper.model_for(ar)
|
169
258
|
|
170
259
|
model.id.must_equal ar.id
|
171
|
-
model.
|
260
|
+
model.attribute.must_equal nil
|
172
261
|
end
|
173
262
|
|
174
|
-
it "
|
175
|
-
model =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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:
|
133
|
+
rubygems_version: 2.0.6
|
94
134
|
signing_key:
|
95
|
-
specification_version:
|
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
|