minimapper 0.3.0 → 0.4.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.
- 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
|