minimapper 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -0
- data/Gemfile +1 -1
- data/README.md +36 -6
- data/lib/minimapper/entity/attributes.rb +1 -0
- data/lib/minimapper/entity/core.rb +9 -1
- data/lib/minimapper/entity/rails.rb +20 -0
- data/lib/minimapper/entity.rb +5 -1
- data/lib/minimapper/mapper/ar.rb +138 -0
- data/lib/minimapper/mapper/common.rb +9 -0
- data/lib/minimapper/mapper/memory.rb +94 -0
- data/lib/minimapper/version.rb +1 -1
- data/spec/mapper/ar_spec.rb +135 -0
- data/spec/support/database_setup.rb +1 -1
- data/spec/support/shared_examples/mapper.rb +6 -6
- data/unit/entity/core_spec.rb +34 -5
- data/unit/entity/rails_spec.rb +11 -0
- data/unit/{memory_spec.rb → mapper/memory_spec.rb} +2 -2
- data/unit/repository_spec.rb +2 -2
- metadata +12 -12
- data/lib/minimapper/ar.rb +0 -118
- data/lib/minimapper/common.rb +0 -7
- data/lib/minimapper/memory.rb +0 -92
- data/spec/ar_spec.rb +0 -67
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -57,7 +57,7 @@ You can use the mappers like this (<strong>it's runnable, try copy and pasting i
|
|
57
57
|
require "rubygems"
|
58
58
|
require "minimapper"
|
59
59
|
require "minimapper/entity"
|
60
|
-
require "minimapper/memory"
|
60
|
+
require "minimapper/mapper/memory"
|
61
61
|
|
62
62
|
class User
|
63
63
|
include Minimapper::Entity
|
@@ -66,7 +66,7 @@ class User
|
|
66
66
|
validates :name, :presence => true
|
67
67
|
end
|
68
68
|
|
69
|
-
class UserMapper < Minimapper::Memory
|
69
|
+
class UserMapper < Minimapper::Mapper::Memory
|
70
70
|
end
|
71
71
|
|
72
72
|
## Creating
|
@@ -89,7 +89,7 @@ old_id = user.id
|
|
89
89
|
user_mapper.delete(user)
|
90
90
|
p user.id # => nil
|
91
91
|
p user_mapper.find_by_id(old_id) # => nil
|
92
|
-
# user_mapper.find(old_id) # raises Minimapper::
|
92
|
+
# user_mapper.find(old_id) # raises Minimapper::Mapper::CanNotFindEntity
|
93
93
|
# user_mapper.delete_all
|
94
94
|
# user_mapper.delete_by_id(1)
|
95
95
|
|
@@ -120,7 +120,7 @@ This is not directly runnable like the previous example, it requires ActiveRecor
|
|
120
120
|
When you do need to use an ORM like ActiveRecord however, it now has the same API as your in-memory persistence (thanks to the [shared tests](https://github.com/joakimk/minimapper/blob/master/spec/support/shared_examples/mapper.rb) which define how a mapper is supposed to behave).
|
121
121
|
|
122
122
|
``` ruby
|
123
|
-
require "minimapper/ar"
|
123
|
+
require "minimapper/mapper/ar"
|
124
124
|
|
125
125
|
module AR
|
126
126
|
class UserMapper < Minimapper::AR
|
@@ -136,6 +136,24 @@ mapper = AR::UserMapper.new
|
|
136
136
|
mapper.create(user)
|
137
137
|
```
|
138
138
|
|
139
|
+
### Uniqueness validations and other DB validations
|
140
|
+
|
141
|
+
Validations on uniqueness can't be implemented on the entity, because they need to access the database.
|
142
|
+
|
143
|
+
Therefore, the ActiveRecord mapper will copy over any record errors to the entity when attempting to create or update.
|
144
|
+
|
145
|
+
You would add these validations to the record itself, like:
|
146
|
+
|
147
|
+
``` ruby
|
148
|
+
class User < ActiveRecord::Base
|
149
|
+
validates :email, :uniqueness => true
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
Note that calling `valid?` on the entity will not access the database. Errors copied over from the record will remain until the next attempt to create or update.
|
154
|
+
|
155
|
+
So an entity that wouldn't be unique in the database will be `valid?` before you attempt to create it. And after you attempt to create it, the entity will not be `valid?` even after assigning a new value, until you attempt to create it again.
|
156
|
+
|
139
157
|
### Custom queries
|
140
158
|
|
141
159
|
You can write custom queries like this:
|
@@ -143,7 +161,7 @@ You can write custom queries like this:
|
|
143
161
|
``` ruby
|
144
162
|
# Memory implementation
|
145
163
|
module Memory
|
146
|
-
class ProjectMapper < Minimapper::Memory
|
164
|
+
class ProjectMapper < Minimapper::Mapper::Memory
|
147
165
|
def waiting_for_review
|
148
166
|
all.find_all { |p| p.waiting_for_review }.sort_by(&:id).reverse
|
149
167
|
end
|
@@ -173,6 +191,19 @@ end
|
|
173
191
|
|
174
192
|
It gets simpler to maintain if you use shared tests to test both implementations. For inspiration, see the [shared tests](https://github.com/joakimk/minimapper/blob/master/spec/support/shared_examples/mapper.rb) used to test minimapper.
|
175
193
|
|
194
|
+
`entity_for` returns nil for nil.
|
195
|
+
|
196
|
+
It takes an optional second argument if you want a different entity class than the mapper's:
|
197
|
+
|
198
|
+
```
|
199
|
+
class ProjectMapper < Minimapper::AR
|
200
|
+
def owner_of(project)
|
201
|
+
owner_record = find(project).owner
|
202
|
+
entity_for(owner_record, User)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
176
207
|
### Typed attributes
|
177
208
|
|
178
209
|
If you specify type, minimapper will attempt to convert into that type. Supported types: Integer and DateTime (:integer and :date_time).
|
@@ -287,7 +318,6 @@ You need mysql and postgres installed (but they do not have to be running) to be
|
|
287
318
|
I won't implement anything that isn't actually used. But here are some ideas for things that might make it into minimapper someday if there is a need for it.
|
288
319
|
|
289
320
|
* Provide a hook to convert attributes between entities and the backing models (when your entity attributes and db-schema isn't a one-to-one match).
|
290
|
-
* Copy validation errors back from the mapper to the entity (for example if you do uniqueness validation in a backing ActiveRecord-model).
|
291
321
|
|
292
322
|
## Credits and license
|
293
323
|
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# Include this in your entity models for
|
2
|
+
# Ruby on Rails conveniences, like being
|
3
|
+
# able to use them in forms.
|
4
|
+
|
1
5
|
require "active_model"
|
2
6
|
|
3
7
|
module Minimapper
|
@@ -7,6 +11,22 @@ module Minimapper
|
|
7
11
|
klass.class_eval do
|
8
12
|
extend ActiveModel::Naming
|
9
13
|
include ActiveModel::Validations
|
14
|
+
|
15
|
+
# Must be later than ActiveModel::Validations so
|
16
|
+
# it can call it with super.
|
17
|
+
include ValidationsWithMapperErrors
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ValidationsWithMapperErrors
|
22
|
+
def valid?
|
23
|
+
super
|
24
|
+
|
25
|
+
mapper_errors.each do |a, v|
|
26
|
+
errors.add(a, v)
|
27
|
+
end
|
28
|
+
|
29
|
+
errors.empty?
|
10
30
|
end
|
11
31
|
end
|
12
32
|
|
data/lib/minimapper/entity.rb
CHANGED
@@ -19,7 +19,11 @@ module Minimapper
|
|
19
19
|
def self.included(klass)
|
20
20
|
klass.send(:include, Minimapper::Entity::Rails)
|
21
21
|
klass.send(:extend, Minimapper::Entity::Attributes)
|
22
|
-
klass.attributes(
|
22
|
+
klass.attributes(
|
23
|
+
[ :id, :integer ],
|
24
|
+
[ :created_at, :date_time ],
|
25
|
+
[ :updated_at, :date_time ]
|
26
|
+
)
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "minimapper/mapper/common"
|
2
|
+
|
3
|
+
module Minimapper
|
4
|
+
module Mapper
|
5
|
+
class AR
|
6
|
+
include Common
|
7
|
+
|
8
|
+
# Create
|
9
|
+
|
10
|
+
def create(entity)
|
11
|
+
record = record_class.new
|
12
|
+
validate_record_and_copy_errors_to_entity(record, entity)
|
13
|
+
|
14
|
+
if entity.valid?
|
15
|
+
record.save!
|
16
|
+
entity.id = record.id
|
17
|
+
entity.id
|
18
|
+
else
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Read
|
24
|
+
|
25
|
+
def find(id)
|
26
|
+
entity_for(find_record_safely(id))
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_by_id(id)
|
30
|
+
entity_for(find_record(id))
|
31
|
+
end
|
32
|
+
|
33
|
+
def all
|
34
|
+
entities_for record_class.all
|
35
|
+
end
|
36
|
+
|
37
|
+
def first
|
38
|
+
entity_for(record_class.order("id ASC").first)
|
39
|
+
end
|
40
|
+
|
41
|
+
def last
|
42
|
+
entity_for(record_class.order("id ASC").last)
|
43
|
+
end
|
44
|
+
|
45
|
+
def count
|
46
|
+
record_class.count
|
47
|
+
end
|
48
|
+
|
49
|
+
# Update
|
50
|
+
|
51
|
+
def update(entity)
|
52
|
+
record = record_for(entity)
|
53
|
+
validate_record_and_copy_errors_to_entity(record, entity)
|
54
|
+
|
55
|
+
if entity.valid?
|
56
|
+
record.save!
|
57
|
+
true
|
58
|
+
else
|
59
|
+
false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Delete
|
64
|
+
|
65
|
+
def delete(entity)
|
66
|
+
delete_by_id(entity.id)
|
67
|
+
entity.id = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_by_id(id)
|
71
|
+
find_record_safely(id).delete
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete_all
|
75
|
+
record_class.delete_all
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# NOTE: Don't memoize the classes or code reloading will break in rails apps.
|
81
|
+
|
82
|
+
# Will attempt to use AR:Project as the record class
|
83
|
+
# when the mapper class name is AR::ProjectMapper
|
84
|
+
def record_class
|
85
|
+
self.class.name.sub(/Mapper$/, '').constantize
|
86
|
+
end
|
87
|
+
|
88
|
+
# Will attempt to use Project as the enity class when
|
89
|
+
# the mapper class name is AR::ProjectMapper
|
90
|
+
def entity_class
|
91
|
+
("::" + self.class.name.split('::').last.sub(/Mapper$/, '')).constantize
|
92
|
+
end
|
93
|
+
|
94
|
+
def accessible_attributes(entity)
|
95
|
+
entity.attributes.reject { |k, v| protected_attributes.include?(k.to_s) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def protected_attributes
|
99
|
+
record_class.protected_attributes
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_record_and_copy_errors_to_entity(record, entity)
|
103
|
+
record.attributes = accessible_attributes(entity)
|
104
|
+
record.valid?
|
105
|
+
entity.mapper_errors = record.errors.map { |k, v| [k, v] }
|
106
|
+
end
|
107
|
+
|
108
|
+
def find_record_safely(id)
|
109
|
+
find_record(id) ||
|
110
|
+
raise(CanNotFindEntity, :id => id)
|
111
|
+
end
|
112
|
+
|
113
|
+
def find_record(id)
|
114
|
+
id && record_class.find_by_id(id)
|
115
|
+
end
|
116
|
+
|
117
|
+
def record_for(entity)
|
118
|
+
(entity.id && record_class.find_by_id(entity.id)) ||
|
119
|
+
raise(CanNotFindEntity, entity.inspect)
|
120
|
+
end
|
121
|
+
|
122
|
+
def entities_for(records)
|
123
|
+
records.map { |record| entity_for(record) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def entity_for(record, klass = entity_class)
|
127
|
+
if record
|
128
|
+
entity = klass.new
|
129
|
+
entity.id = record.id
|
130
|
+
entity.attributes = record.attributes.symbolize_keys
|
131
|
+
entity
|
132
|
+
else
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'minimapper/mapper/common'
|
2
|
+
|
3
|
+
module Minimapper
|
4
|
+
module Mapper
|
5
|
+
class Memory
|
6
|
+
include Common
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@store = []
|
10
|
+
@last_id = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create
|
14
|
+
def create(entity)
|
15
|
+
if entity.valid?
|
16
|
+
entity.id = next_id
|
17
|
+
store.push(entity.dup)
|
18
|
+
last_id
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Read
|
25
|
+
def find(id)
|
26
|
+
find_internal_safely(id).dup
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_by_id(id)
|
30
|
+
entity = find_internal(id)
|
31
|
+
entity && entity.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
def all
|
35
|
+
store.map { |entity| entity.dup }
|
36
|
+
end
|
37
|
+
|
38
|
+
def first
|
39
|
+
store.first && store.first.dup
|
40
|
+
end
|
41
|
+
|
42
|
+
def last
|
43
|
+
store.last && store.last.dup
|
44
|
+
end
|
45
|
+
|
46
|
+
def count
|
47
|
+
all.size
|
48
|
+
end
|
49
|
+
|
50
|
+
# Update
|
51
|
+
def update(entity)
|
52
|
+
if entity.valid?
|
53
|
+
known_entity = find_internal_safely(entity.id)
|
54
|
+
known_entity.attributes = entity.attributes
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Delete
|
62
|
+
def delete(entity)
|
63
|
+
delete_by_id(entity.id)
|
64
|
+
entity.id = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_by_id(id)
|
68
|
+
entity = find_internal_safely(id)
|
69
|
+
store.delete(entity)
|
70
|
+
end
|
71
|
+
|
72
|
+
def delete_all
|
73
|
+
store.clear
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def find_internal_safely(id)
|
79
|
+
find_internal(id) ||
|
80
|
+
raise(CanNotFindEntity, :id => id)
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_internal(id)
|
84
|
+
id && store.find { |e| e.id == id.to_i }
|
85
|
+
end
|
86
|
+
|
87
|
+
def next_id
|
88
|
+
@last_id += 1
|
89
|
+
end
|
90
|
+
|
91
|
+
attr_reader :store, :last_id
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/minimapper/version.rb
CHANGED
@@ -0,0 +1,135 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "minimapper/entity/core"
|
3
|
+
require "minimapper/mapper/ar"
|
4
|
+
|
5
|
+
class TestEntity
|
6
|
+
include Minimapper::Entity::Core
|
7
|
+
end
|
8
|
+
|
9
|
+
class TestMapper < Minimapper::Mapper::AR
|
10
|
+
private
|
11
|
+
|
12
|
+
def entity_class
|
13
|
+
TestEntity
|
14
|
+
end
|
15
|
+
|
16
|
+
def record_class
|
17
|
+
Record
|
18
|
+
end
|
19
|
+
|
20
|
+
class Record < ActiveRecord::Base
|
21
|
+
attr_protected :visible
|
22
|
+
|
23
|
+
validates :email,
|
24
|
+
:uniqueness => true,
|
25
|
+
:allow_nil => true
|
26
|
+
|
27
|
+
self.table_name = :projects
|
28
|
+
self.mass_assignment_sanitizer = :strict
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe Minimapper::Mapper::AR do
|
33
|
+
let(:mapper) { TestMapper.new }
|
34
|
+
let(:entity_class) { TestEntity }
|
35
|
+
|
36
|
+
include_examples :mapper
|
37
|
+
|
38
|
+
describe "#create" do
|
39
|
+
it "does not include protected attributes" do
|
40
|
+
# because it leads to exceptions when mass_assignment_sanitizer is set to strict
|
41
|
+
entity = build_entity(:visible => true, :name => "Joe")
|
42
|
+
mapper.create(entity)
|
43
|
+
|
44
|
+
stored_entity = mapper.find(entity.id)
|
45
|
+
stored_entity.attributes[:visible].should be_nil
|
46
|
+
stored_entity.attributes[:name].should == "Joe"
|
47
|
+
|
48
|
+
entity = TestEntity.new
|
49
|
+
entity.attributes = { :visible => true, :name => "Joe" }
|
50
|
+
TestMapper::Record.stub(:protected_attributes => [])
|
51
|
+
lambda { mapper.create(entity) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "copies record validation errors to entity" do
|
55
|
+
old_entity = build_entity(:email => "joe@example.com")
|
56
|
+
mapper.create(old_entity)
|
57
|
+
old_entity.mapper_errors.should == []
|
58
|
+
|
59
|
+
new_entity = build_entity(:email => "joe@example.com")
|
60
|
+
mapper.create(new_entity)
|
61
|
+
new_entity.mapper_errors.should == [ [:email, "has already been taken"] ]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "can revalidate on record validation errors" do
|
65
|
+
old_entity = build_entity(:email => "joe@example.com")
|
66
|
+
mapper.create(old_entity)
|
67
|
+
|
68
|
+
new_entity = build_entity(:email => "joe@example.com")
|
69
|
+
mapper.create(new_entity)
|
70
|
+
new_entity.mapper_errors.should == [ [:email, "has already been taken"] ]
|
71
|
+
|
72
|
+
new_entity.attributes = { :email => "something.else@example.com" }
|
73
|
+
mapper.create(new_entity)
|
74
|
+
new_entity.should be_valid
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_entity(attributes)
|
78
|
+
entity = TestEntity.new
|
79
|
+
entity.attributes = attributes
|
80
|
+
entity
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#update" do
|
85
|
+
it "does not include protected attributes" do
|
86
|
+
entity = TestEntity.new
|
87
|
+
mapper.create(entity)
|
88
|
+
|
89
|
+
entity.attributes = { :visible => true, :name => "Joe" }
|
90
|
+
mapper.update(entity)
|
91
|
+
stored_entity = mapper.find(entity.id)
|
92
|
+
stored_entity.attributes[:visible].should be_nil
|
93
|
+
stored_entity.attributes[:name].should == "Joe"
|
94
|
+
|
95
|
+
TestMapper::Record.stub(:protected_attributes => [])
|
96
|
+
lambda { mapper.update(entity) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "copies record validation errors to entity" do
|
100
|
+
old_entity = build_entity(:email => "joe@example.com")
|
101
|
+
mapper.create(old_entity)
|
102
|
+
|
103
|
+
new_entity = TestEntity.new
|
104
|
+
mapper.create(new_entity)
|
105
|
+
new_entity.mapper_errors.should == []
|
106
|
+
|
107
|
+
new_entity.attributes = { :email => "joe@example.com" }
|
108
|
+
mapper.update(new_entity)
|
109
|
+
new_entity.mapper_errors.should == [ [:email, "has already been taken"] ]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "can revalidate on record validation errors" do
|
113
|
+
old_entity = build_entity(:email => "joe@example.com")
|
114
|
+
mapper.create(old_entity)
|
115
|
+
|
116
|
+
new_entity = TestEntity.new
|
117
|
+
mapper.create(new_entity)
|
118
|
+
new_entity.mapper_errors.should == []
|
119
|
+
|
120
|
+
new_entity.attributes = { :email => "joe@example.com" }
|
121
|
+
mapper.update(new_entity)
|
122
|
+
new_entity.mapper_errors.should == [ [:email, "has already been taken"] ]
|
123
|
+
|
124
|
+
new_entity.attributes = { :email => "something.else@example.com" }
|
125
|
+
mapper.update(new_entity)
|
126
|
+
new_entity.should be_valid
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_entity(attributes)
|
130
|
+
entity = TestEntity.new
|
131
|
+
entity.attributes = attributes
|
132
|
+
entity
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -64,7 +64,7 @@ shared_examples :mapper do
|
|
64
64
|
end
|
65
65
|
|
66
66
|
it "fails when an entity can not be found" do
|
67
|
-
lambda { mapper.find(-1) }.should raise_error(Minimapper::
|
67
|
+
lambda { mapper.find(-1) }.should raise_error(Minimapper::Mapper::CanNotFindEntity)
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -199,14 +199,14 @@ shared_examples :mapper do
|
|
199
199
|
|
200
200
|
it "fails when the entity does not have an id" do
|
201
201
|
entity = build_valid_entity
|
202
|
-
lambda { mapper.update(entity) }.should raise_error(Minimapper::
|
202
|
+
lambda { mapper.update(entity) }.should raise_error(Minimapper::Mapper::CanNotFindEntity)
|
203
203
|
end
|
204
204
|
|
205
205
|
it "fails when the entity no longer exists" do
|
206
206
|
entity = build_valid_entity
|
207
207
|
mapper.create(entity)
|
208
208
|
mapper.delete_all
|
209
|
-
lambda { mapper.update(entity) }.should raise_error(Minimapper::
|
209
|
+
lambda { mapper.update(entity) }.should raise_error(Minimapper::Mapper::CanNotFindEntity)
|
210
210
|
end
|
211
211
|
end
|
212
212
|
|
@@ -231,13 +231,13 @@ shared_examples :mapper do
|
|
231
231
|
|
232
232
|
it "fails when the entity does not have an id" do
|
233
233
|
entity = entity_class.new
|
234
|
-
lambda { mapper.delete(entity) }.should raise_error(Minimapper::
|
234
|
+
lambda { mapper.delete(entity) }.should raise_error(Minimapper::Mapper::CanNotFindEntity)
|
235
235
|
end
|
236
236
|
|
237
237
|
it "fails when the entity can not be found" do
|
238
238
|
entity = entity_class.new
|
239
239
|
entity.id = -1
|
240
|
-
lambda { mapper.delete(entity) }.should raise_error(Minimapper::
|
240
|
+
lambda { mapper.delete(entity) }.should raise_error(Minimapper::Mapper::CanNotFindEntity)
|
241
241
|
end
|
242
242
|
end
|
243
243
|
|
@@ -252,7 +252,7 @@ shared_examples :mapper do
|
|
252
252
|
end
|
253
253
|
|
254
254
|
it "fails when an entity can not be found" do
|
255
|
-
lambda { mapper.delete_by_id(-1) }.should raise_error(Minimapper::
|
255
|
+
lambda { mapper.delete_by_id(-1) }.should raise_error(Minimapper::Mapper::CanNotFindEntity)
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
data/unit/entity/core_spec.rb
CHANGED
@@ -27,14 +27,43 @@ describe Minimapper::Entity::Core do
|
|
27
27
|
entity.attributes.should == { :one => 11 }
|
28
28
|
end
|
29
29
|
|
30
|
-
it "returns true for valid?" do
|
31
|
-
entity = BasicEntity.new
|
32
|
-
entity.should be_valid
|
33
|
-
end
|
34
|
-
|
35
30
|
it "responds to id" do
|
36
31
|
entity = BasicEntity.new
|
37
32
|
entity.id = 10
|
38
33
|
entity.id.should == 10
|
39
34
|
end
|
35
|
+
|
36
|
+
describe "#mapper_errors" do
|
37
|
+
it "defaults to an empty array" do
|
38
|
+
entity = BasicEntity.new
|
39
|
+
entity.mapper_errors.should == []
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can be changed" do
|
43
|
+
entity = BasicEntity.new
|
44
|
+
entity.mapper_errors = [ [:one, "bad"] ]
|
45
|
+
entity.mapper_errors.should == [ [:one, "bad"] ]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#mapper_errors=" do
|
50
|
+
it "makes it invalid if present" do
|
51
|
+
entity = BasicEntity.new
|
52
|
+
entity.mapper_errors = [ [:one, "bad"] ]
|
53
|
+
entity.valid?.should be_false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#valid?" do
|
58
|
+
it "is true without errors" do
|
59
|
+
entity = BasicEntity.new
|
60
|
+
entity.valid?.should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "is false with errors" do
|
64
|
+
entity = BasicEntity.new
|
65
|
+
entity.mapper_errors = [ [:one, "bad"] ]
|
66
|
+
entity.valid?.should be_false
|
67
|
+
end
|
68
|
+
end
|
40
69
|
end
|
data/unit/entity/rails_spec.rb
CHANGED
@@ -50,4 +50,15 @@ describe Minimapper::Entity::Rails do
|
|
50
50
|
entity.name = "Joe"
|
51
51
|
entity.should be_valid
|
52
52
|
end
|
53
|
+
|
54
|
+
describe "#mapper_errors=" do
|
55
|
+
it "adds an error to the errors collection" do
|
56
|
+
entity = RailsEntity.new
|
57
|
+
entity.name = "Joe"
|
58
|
+
entity.should be_valid
|
59
|
+
entity.mapper_errors = [ [:name, "must be unique"] ]
|
60
|
+
entity.should_not be_valid
|
61
|
+
entity.errors[:name].should == ["must be unique"]
|
62
|
+
end
|
63
|
+
end
|
53
64
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'minimapper/memory'
|
1
|
+
require 'minimapper/mapper/memory'
|
2
2
|
require 'minimapper/entity/core'
|
3
3
|
|
4
4
|
class BasicEntity
|
5
5
|
include Minimapper::Entity::Core
|
6
6
|
end
|
7
7
|
|
8
|
-
describe Minimapper::Memory do
|
8
|
+
describe Minimapper::Mapper::Memory do
|
9
9
|
let(:mapper) { described_class.new }
|
10
10
|
let(:entity_class) { BasicEntity }
|
11
11
|
|
data/unit/repository_spec.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minimapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70108741422080 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70108741422080
|
25
25
|
description: A minimalistic way of separating your models from ORMs like ActiveRecord.
|
26
26
|
email:
|
27
27
|
- joakim.kolsjo@gmail.com
|
@@ -37,8 +37,6 @@ files:
|
|
37
37
|
- README.md
|
38
38
|
- Rakefile
|
39
39
|
- lib/minimapper.rb
|
40
|
-
- lib/minimapper/ar.rb
|
41
|
-
- lib/minimapper/common.rb
|
42
40
|
- lib/minimapper/entity.rb
|
43
41
|
- lib/minimapper/entity/attributes.rb
|
44
42
|
- lib/minimapper/entity/convert.rb
|
@@ -46,13 +44,15 @@ files:
|
|
46
44
|
- lib/minimapper/entity/convert/to_integer.rb
|
47
45
|
- lib/minimapper/entity/core.rb
|
48
46
|
- lib/minimapper/entity/rails.rb
|
49
|
-
- lib/minimapper/
|
47
|
+
- lib/minimapper/mapper/ar.rb
|
48
|
+
- lib/minimapper/mapper/common.rb
|
49
|
+
- lib/minimapper/mapper/memory.rb
|
50
50
|
- lib/minimapper/repository.rb
|
51
51
|
- lib/minimapper/version.rb
|
52
52
|
- minimapper.gemspec
|
53
53
|
- script/ci
|
54
54
|
- script/turbux_rspec
|
55
|
-
- spec/ar_spec.rb
|
55
|
+
- spec/mapper/ar_spec.rb
|
56
56
|
- spec/spec_helper.rb
|
57
57
|
- spec/support/database_setup.rb
|
58
58
|
- spec/support/shared_examples/mapper.rb
|
@@ -60,7 +60,7 @@ files:
|
|
60
60
|
- unit/entity/core_spec.rb
|
61
61
|
- unit/entity/rails_spec.rb
|
62
62
|
- unit/entity_spec.rb
|
63
|
-
- unit/memory_spec.rb
|
63
|
+
- unit/mapper/memory_spec.rb
|
64
64
|
- unit/repository_spec.rb
|
65
65
|
- unit/spec_helper.rb
|
66
66
|
homepage: ''
|
@@ -77,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
77
|
version: '0'
|
78
78
|
segments:
|
79
79
|
- 0
|
80
|
-
hash:
|
80
|
+
hash: 3446705768728463359
|
81
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
82
|
none: false
|
83
83
|
requirements:
|
@@ -86,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
86
|
version: '0'
|
87
87
|
segments:
|
88
88
|
- 0
|
89
|
-
hash:
|
89
|
+
hash: 3446705768728463359
|
90
90
|
requirements: []
|
91
91
|
rubyforge_project:
|
92
92
|
rubygems_version: 1.8.5
|
@@ -94,7 +94,7 @@ signing_key:
|
|
94
94
|
specification_version: 3
|
95
95
|
summary: A minimalistic way of separating your models from ORMs like ActiveRecord.
|
96
96
|
test_files:
|
97
|
-
- spec/ar_spec.rb
|
97
|
+
- spec/mapper/ar_spec.rb
|
98
98
|
- spec/spec_helper.rb
|
99
99
|
- spec/support/database_setup.rb
|
100
100
|
- spec/support/shared_examples/mapper.rb
|
data/lib/minimapper/ar.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
require "minimapper/common"
|
2
|
-
|
3
|
-
module Minimapper
|
4
|
-
class AR
|
5
|
-
include Common
|
6
|
-
|
7
|
-
# Create
|
8
|
-
def create(entity)
|
9
|
-
if entity.valid?
|
10
|
-
entity.id = record_class.create!(accessible_attributes(entity)).id
|
11
|
-
else
|
12
|
-
false
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# Read
|
17
|
-
def find(id)
|
18
|
-
entity_for(find_record_safely(id))
|
19
|
-
end
|
20
|
-
|
21
|
-
def find_by_id(id)
|
22
|
-
entity_for(find_record(id))
|
23
|
-
end
|
24
|
-
|
25
|
-
def all
|
26
|
-
entities_for record_class.all
|
27
|
-
end
|
28
|
-
|
29
|
-
def first
|
30
|
-
entity_for(record_class.order("id ASC").first)
|
31
|
-
end
|
32
|
-
|
33
|
-
def last
|
34
|
-
entity_for(record_class.order("id ASC").last)
|
35
|
-
end
|
36
|
-
|
37
|
-
def count
|
38
|
-
record_class.count
|
39
|
-
end
|
40
|
-
|
41
|
-
# Update
|
42
|
-
def update(entity)
|
43
|
-
if entity.valid?
|
44
|
-
record_for(entity).update_attributes!(accessible_attributes(entity))
|
45
|
-
true
|
46
|
-
else
|
47
|
-
false
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Delete
|
52
|
-
def delete(entity)
|
53
|
-
delete_by_id(entity.id)
|
54
|
-
entity.id = nil
|
55
|
-
end
|
56
|
-
|
57
|
-
def delete_by_id(id)
|
58
|
-
find_record_safely(id).delete
|
59
|
-
end
|
60
|
-
|
61
|
-
def delete_all
|
62
|
-
record_class.delete_all
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
# NOTE: Don't memoize the classes or code reloading will break in rails apps.
|
68
|
-
|
69
|
-
# Will attempt to use AR:Project as the record class
|
70
|
-
# when the mapper class name is AR::ProjectMapper
|
71
|
-
def record_class
|
72
|
-
self.class.name.gsub(/Mapper/, '').constantize
|
73
|
-
end
|
74
|
-
|
75
|
-
# Will attempt to use Project as the enity class when
|
76
|
-
# the mapper class name is AR::ProjectMapper
|
77
|
-
def entity_class
|
78
|
-
("::" + self.class.name.split('::').last.gsub(/Mapper/, '')).constantize
|
79
|
-
end
|
80
|
-
|
81
|
-
def accessible_attributes(entity)
|
82
|
-
entity.attributes.reject { |k, v| protected_attributes.include?(k.to_s) }
|
83
|
-
end
|
84
|
-
|
85
|
-
def protected_attributes
|
86
|
-
record_class.protected_attributes
|
87
|
-
end
|
88
|
-
|
89
|
-
def find_record_safely(id)
|
90
|
-
find_record(id) ||
|
91
|
-
raise(Common::CanNotFindEntity, :id => id)
|
92
|
-
end
|
93
|
-
|
94
|
-
def find_record(id)
|
95
|
-
id && record_class.find_by_id(id)
|
96
|
-
end
|
97
|
-
|
98
|
-
def record_for(entity)
|
99
|
-
(entity.id && record_class.find_by_id(entity.id)) ||
|
100
|
-
raise(Common::CanNotFindEntity, entity.inspect)
|
101
|
-
end
|
102
|
-
|
103
|
-
def entities_for(records)
|
104
|
-
records.map { |record| entity_for(record) }
|
105
|
-
end
|
106
|
-
|
107
|
-
def entity_for(record)
|
108
|
-
if record
|
109
|
-
entity = entity_class.new
|
110
|
-
entity.id = record.id
|
111
|
-
entity.attributes = record.attributes.symbolize_keys
|
112
|
-
entity
|
113
|
-
else
|
114
|
-
nil
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
data/lib/minimapper/common.rb
DELETED
data/lib/minimapper/memory.rb
DELETED
@@ -1,92 +0,0 @@
|
|
1
|
-
require 'minimapper/common'
|
2
|
-
|
3
|
-
module Minimapper
|
4
|
-
class Memory
|
5
|
-
include Common
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@store = []
|
9
|
-
@last_id = 0
|
10
|
-
end
|
11
|
-
|
12
|
-
# Create
|
13
|
-
def create(entity)
|
14
|
-
if entity.valid?
|
15
|
-
entity.id = next_id
|
16
|
-
store.push(entity.dup)
|
17
|
-
last_id
|
18
|
-
else
|
19
|
-
false
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# Read
|
24
|
-
def find(id)
|
25
|
-
find_internal_safely(id).dup
|
26
|
-
end
|
27
|
-
|
28
|
-
def find_by_id(id)
|
29
|
-
entity = find_internal(id)
|
30
|
-
entity && entity.dup
|
31
|
-
end
|
32
|
-
|
33
|
-
def all
|
34
|
-
store.map { |entity| entity.dup }
|
35
|
-
end
|
36
|
-
|
37
|
-
def first
|
38
|
-
store.first && store.first.dup
|
39
|
-
end
|
40
|
-
|
41
|
-
def last
|
42
|
-
store.last && store.last.dup
|
43
|
-
end
|
44
|
-
|
45
|
-
def count
|
46
|
-
all.size
|
47
|
-
end
|
48
|
-
|
49
|
-
# Update
|
50
|
-
def update(entity)
|
51
|
-
if entity.valid?
|
52
|
-
known_entity = find_internal_safely(entity.id)
|
53
|
-
known_entity.attributes = entity.attributes
|
54
|
-
true
|
55
|
-
else
|
56
|
-
false
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# Delete
|
61
|
-
def delete(entity)
|
62
|
-
delete_by_id(entity.id)
|
63
|
-
entity.id = nil
|
64
|
-
end
|
65
|
-
|
66
|
-
def delete_by_id(id)
|
67
|
-
entity = find_internal_safely(id)
|
68
|
-
store.delete(entity)
|
69
|
-
end
|
70
|
-
|
71
|
-
def delete_all
|
72
|
-
store.clear
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def find_internal_safely(id)
|
78
|
-
find_internal(id) ||
|
79
|
-
raise(Common::CanNotFindEntity, :id => id)
|
80
|
-
end
|
81
|
-
|
82
|
-
def find_internal(id)
|
83
|
-
id && store.find { |e| e.id == id.to_i }
|
84
|
-
end
|
85
|
-
|
86
|
-
def next_id
|
87
|
-
@last_id += 1
|
88
|
-
end
|
89
|
-
|
90
|
-
attr_reader :store, :last_id
|
91
|
-
end
|
92
|
-
end
|
data/spec/ar_spec.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
require "minimapper/entity/core"
|
3
|
-
require "minimapper/ar"
|
4
|
-
|
5
|
-
class TestEntity
|
6
|
-
include Minimapper::Entity::Core
|
7
|
-
end
|
8
|
-
|
9
|
-
class TestMapper < Minimapper::AR
|
10
|
-
private
|
11
|
-
|
12
|
-
def entity_class
|
13
|
-
TestEntity
|
14
|
-
end
|
15
|
-
|
16
|
-
def record_class
|
17
|
-
Record
|
18
|
-
end
|
19
|
-
|
20
|
-
class Record < ActiveRecord::Base
|
21
|
-
attr_protected :visible
|
22
|
-
|
23
|
-
self.table_name = :projects
|
24
|
-
self.mass_assignment_sanitizer = :strict
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
describe Minimapper::AR do
|
29
|
-
let(:mapper) { TestMapper.new }
|
30
|
-
let(:entity_class) { TestEntity }
|
31
|
-
|
32
|
-
include_examples :mapper
|
33
|
-
|
34
|
-
describe "#create" do
|
35
|
-
it "does not include protected attributes" do
|
36
|
-
# because it leads to exceptions when mass_assignment_sanitizer is set to strict
|
37
|
-
entity = TestEntity.new
|
38
|
-
entity.attributes = { :visible => true, :name => "Joe" }
|
39
|
-
mapper.create(entity)
|
40
|
-
|
41
|
-
stored_entity = mapper.find(entity.id)
|
42
|
-
stored_entity.attributes[:visible].should be_nil
|
43
|
-
stored_entity.attributes[:name].should == "Joe"
|
44
|
-
|
45
|
-
entity = TestEntity.new
|
46
|
-
entity.attributes = { :visible => true, :name => "Joe" }
|
47
|
-
TestMapper::Record.stub(:protected_attributes => [])
|
48
|
-
lambda { mapper.create(entity) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "#update" do
|
53
|
-
it "does not include protected attributes" do
|
54
|
-
entity = TestEntity.new
|
55
|
-
mapper.create(entity)
|
56
|
-
|
57
|
-
entity.attributes = { :visible => true, :name => "Joe" }
|
58
|
-
mapper.update(entity)
|
59
|
-
stored_entity = mapper.find(entity.id)
|
60
|
-
stored_entity.attributes[:visible].should be_nil
|
61
|
-
stored_entity.attributes[:name].should == "Joe"
|
62
|
-
|
63
|
-
TestMapper::Record.stub(:protected_attributes => [])
|
64
|
-
lambda { mapper.update(entity) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|