minimapper 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -7,28 +7,30 @@
7
7
 
8
8
  ### Introduction
9
9
 
10
- Minimapper is a minimalistic way of separating models from ORMs like ActiveRecord. It enables you to test your models (and code using your models) within a [sub-second unit test suite](https://github.com/joakimk/fast_unit_tests_example) and makes it simpler to have a modular design as described in [Matt Wynne's Hexagonal Rails posts](http://blog.mattwynne.net/2012/04/09/hexagonal-rails-introduction/).
10
+ Minimapper is a minimalistic way of [separating models](http://martinfowler.com/eaaCatalog/dataMapper.html) from ActiveRecord. It enables you to test your models (and code using your models) within a [sub-second unit test suite](https://github.com/joakimk/fast_unit_tests_example) and makes it simpler to have a modular design as described in [Matt Wynne's Hexagonal Rails posts](http://blog.mattwynne.net/2012/04/09/hexagonal-rails-introduction/).
11
11
 
12
- Minimapper comes with an in-memory implementation of common CRUD operations. You can use this in tests to not hit the database where it isn't nessesary to do so. You can also develop new features without having to think about migrations until you need to persist data.
13
-
14
- Minimapper is not an ORM, instead it's a tool to make it simpler to handle persistence in existing applications using ORMs like ActiveRecord. It may also be an attractive alternative to using DataMapper 2 (when it's done) for new apps if you already know ActiveRecord well (most of the rails developers I know have many years of experience with ActiveRecord).
12
+ Minimapper follows many Rails conventions but it does not require Rails.
15
13
 
16
14
  ### Early days
17
15
 
18
16
  The API may not be entirely stable yet and there are probably edge cases that aren't covered. However... it's most likely better to use this than to roll your own project specific solution. We need good tools for this kind of thing in the rails community, but to make that possible we need to gather around a few of them and make them good.
19
17
 
18
+ ### Important resources
19
+
20
+ - [minimapper-extras](https://github.com/barsoom/minimapper-extras) (useful tools for projects using minimapper)
21
+ - [Gist of yet to be extracted mapper code](https://gist.github.com/joakimk/5656945) from a project using minimapper.
22
+
20
23
  ### Compatibility
21
24
 
22
- This gem is tested against all major rubies in 1.8, 1.9 and 2.0, see [.travis.yml](https://github.com/joakimk/minimapper/blob/master/.travis.yml). For each ruby version, the SQL mappers are tested against SQLite3, PostgreSQL and MySQL.
25
+ This gem is tested against all major rubies in 1.8, 1.9 and 2.0, see [.travis.yml](https://github.com/joakimk/minimapper/blob/master/.travis.yml). For each ruby version, the mapper is tested against SQLite3, PostgreSQL and MySQL.
23
26
 
24
27
  ### Only the most basic API
25
28
 
26
29
  This library only implements the most basic persistence API (mostly just CRUD). Any significant additions will be made into separate gems (like [minimapper-extras](https://github.com/barsoom/minimapper-extras)). The reasons for this are:
27
30
 
28
- * It should have a stable API
29
- * It should be possible to learn all it does in a short time
30
- * It should be simple to add an adapter for a new database
31
31
  * It should be simple to maintain minimapper
32
+ * It should be possible to learn all it does in a short time
33
+ * It should have a stable API
32
34
 
33
35
  ## Installation
34
36
 
@@ -52,15 +54,15 @@ Please avoid installing directly from the github repository. Code will be pushed
52
54
 
53
55
  ### Basics
54
56
 
55
- You can use the mappers like this (<strong>it's runnable, try copy and pasting it into a ruby file</strong> or [use this gist](https://gist.github.com/3904952)):
57
+ Basics and how we use minimapper in practice.
56
58
 
57
59
  ``` ruby
58
- # minimapper_example.rb
59
60
  require "rubygems"
60
61
  require "minimapper"
61
62
  require "minimapper/entity"
62
- require "minimapper/mapper/memory"
63
+ require "minimapper/mapper"
63
64
 
65
+ # app/models/user.rb
64
66
  class User
65
67
  include Minimapper::Entity
66
68
 
@@ -68,7 +70,11 @@ class User
68
70
  validates :name, :presence => true
69
71
  end
70
72
 
71
- class UserMapper < Minimapper::Mapper::Memory
73
+ # app/mappers/user_mapper.rb
74
+ class UserMapper < Minimapper::Mapper
75
+ class Record < ActiveRecord::Base
76
+ self.table_name = "users"
77
+ end
72
78
  end
73
79
 
74
80
  ## Creating
@@ -83,6 +89,7 @@ p user_mapper.first.name # => Joe
83
89
 
84
90
  ## Updating
85
91
  user.name = "Joey"
92
+ # user.attributes = params[:user]
86
93
  user_mapper.update(user)
87
94
  p user_mapper.first.name # => Joey
88
95
 
@@ -98,51 +105,33 @@ p user_mapper.find_by_id(old_id) # => nil
98
105
  ## Using a repository
99
106
  require "minimapper/repository"
100
107
 
101
- repository = Minimapper::Repository.build({
108
+ # config/initializers/repository.rb
109
+ Repository = Minimapper::Repository.build({
102
110
  :users => UserMapper.new
103
111
  # :projects => ProjectMapper.new
104
112
  })
105
113
 
106
114
  user = User.new(:name => "Joe")
107
- repository.users.create(user)
115
+ Repository.users.create(user)
108
116
  p repository.users.find(user.id).name # => Joe
109
- repository.users.delete_all
117
+ Repository.users.delete_all
110
118
 
111
119
  ## Using ActiveModel validations
112
120
  user = User.new
113
- repository.users.create(user)
114
- p repository.users.count # => 0
121
+ Repository.users.create(user)
122
+ p Repository.users.count # => 0
115
123
  p user.errors.full_messages # Name can't be blank
116
124
  ```
117
125
 
118
- ### ActiveRecord
126
+ ### Loading all the data you need before acting on it
119
127
 
120
- This is not directly runnable like the previous example, it requires ActiveRecord, a database and a users table. Isn't it interesting how much you could do without those things in the previous example? :)
121
-
122
- 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).
123
-
124
- ``` ruby
125
- require "minimapper/mapper/ar"
126
-
127
- module AR
128
- class UserMapper < Minimapper::AR
129
- end
130
-
131
- class User < ActiveRecord::Base
132
- attr_accessible :name, :email
133
- end
134
- end
135
-
136
- user = User.new(name: "Joe")
137
- mapper = AR::UserMapper.new
138
- mapper.create(user)
139
- ```
128
+ When using minimapper you generally want to load all data you need upfront whenever possible as you don't have lazy loading. We haven't gotten around to adding the inclusion syntax yet, but [it's quite simple to implement](https://gist.github.com/joakimk/5656945).
140
129
 
141
130
  ### Uniqueness validations and other DB validations
142
131
 
143
132
  Validations on uniqueness can't be implemented on the entity, because they need to access the database.
144
133
 
145
- Therefore, the ActiveRecord mapper will copy over any record errors to the entity when attempting to create or update.
134
+ Therefore, the mapper will copy over any record errors to the entity when attempting to create or update.
146
135
 
147
136
  Add these validations to the record itself, like:
148
137
 
@@ -161,21 +150,9 @@ So an entity that wouldn't be unique in the database will be `valid?` before you
161
150
  You can write custom queries like this:
162
151
 
163
152
  ``` ruby
164
- # Memory implementation
165
- module Memory
166
- class ProjectMapper < Minimapper::Mapper::Memory
167
- def waiting_for_review
168
- all.find_all { |p| p.waiting_for_review }.sort_by(&:id).reverse
169
- end
170
- end
171
- end
172
-
173
- # ActiveRecord implementation
174
- module AR
175
- class ProjectMapper < Minimapper::AR
176
- def waiting_for_review
177
- entities_for record_class.where(waiting_for_review: true).order("id DESC")
178
- end
153
+ class ProjectMapper < Minimapper::AR
154
+ def waiting_for_review
155
+ entities_for record_class.where(waiting_for_review: true).order("id DESC")
179
156
  end
180
157
  end
181
158
  ```
@@ -183,14 +160,12 @@ end
183
160
  And then use it like this:
184
161
 
185
162
  ``` ruby
186
- # repository = Minimapper::Repository.build(...)
187
- repository.projects.waiting_for_review.each do |project|
163
+ # Repository = Minimapper::Repository.build(...)
164
+ Repository.projects.waiting_for_review.each do |project|
188
165
  p project.name
189
166
  end
190
167
  ```
191
168
 
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.
193
-
194
169
  `entity_for` returns nil for nil.
195
170
 
196
171
  It takes an optional second argument if you want a different entity class than the mapper's:
@@ -204,9 +179,14 @@ class ProjectMapper < Minimapper::AR
204
179
  end
205
180
  ```
206
181
 
207
- ### Typed attributes
182
+ ### Typed attributes and type coercion
183
+
184
+ If you specify type, Minimapper will only allow values of that type, or strings that can be coerced into that type.
208
185
 
209
- If you specify type, minimapper will attempt to convert into that type. Supported types: Integer and DateTime (:integer and :date_time).
186
+ The latter means that it can accept e.g. string integers directly from a form.
187
+ Minimapper aims to be much less of a form value parser than ActiveRecord, but we'll allow ourselves conveniences like this.
188
+
189
+ Supported types: Integer and DateTime (`:integer` and `:date_time`).
210
190
 
211
191
  ``` ruby
212
192
  class User
@@ -303,17 +283,6 @@ end
303
283
 
304
284
  [Minimapper::Entity](https://github.com/joakimk/minimapper/blob/master/lib/minimapper/entity.rb) adds some convenience methods for when a model is used within a Rails application. If you don't need that you can just include the core API from the [Minimapper::Entity::Core](https://github.com/joakimk/minimapper/blob/master/lib/minimapper/entity/core.rb) module (or implement your own version that behaves like [Minimapper::Entity::Core](https://github.com/joakimk/minimapper/blob/master/lib/minimapper/entity/core.rb)).
305
285
 
306
- ### Adding a new mapper
307
-
308
- If you were to add a [Mongoid](http://mongoid.org/en/mongoid/index.html) mapper:
309
-
310
- 1. Start by copying *spec/ar_spec.rb* to *spec/mongoid_spec.rb* and adapt it for Mongoid.
311
- 2. Add any setup code needed in *spec/support/database_setup.rb*.
312
- 3. Get the [shared tests](https://github.com/joakimk/minimapper/blob/master/spec/support/shared_examples/mapper.rb) to pass for *spec/mongoid_spec.rb*.
313
- 4. Ensure all other tests pass.
314
- 5. Send a pull request.
315
- 6. As soon as it can be made to work in travis in all ruby versions that apply (in Mongoid's case that is only the 1.9 rubies), I'll merge it in.
316
-
317
286
  ## Inspiration
318
287
 
319
288
  ### People
@@ -336,7 +305,7 @@ Robert "Uncle Bob" Martin:
336
305
 
337
306
  ### Running the tests
338
307
 
339
- You need mysql and postgres installed (but they do not have to be running) to be able to run bundle. The sql-mapper tests use sqlite3 by default.
308
+ You need mysql and postgres installed (but they do not have to be running) to be able to run bundle. The mapper tests use sqlite3 by default.
340
309
 
341
310
  bundle
342
311
  rake
@@ -355,19 +324,7 @@ You need mysql and postgres installed (but they do not have to be running) to be
355
324
 
356
325
  ### Next
357
326
 
358
- * Make it possible to override minimapper attributes with super (like it's done in https://github.com/barsoom/traco)
359
327
  * Support default values for attributes (probably only using lambdas to avoid bugs).
360
- * Built in way to set induvidual attributes in a way that bypasses protected attributes like you can do with an AR model.
361
- - user.is_admin = true; user_mapper.update(user) should probably set is_admin to true, mass-assignment should not.
362
- * Extract entity and model class lookup code from the ar-mapper and reuse it in the memory mapper.
363
- * Change the memory mapper to store entity attributes, not entity instances.
364
- - Unless this makes it difficult to handle associated data.
365
-
366
- ### Ideas
367
-
368
- 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.
369
-
370
- * 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).
371
328
 
372
329
  ## Credits and license
373
330
 
@@ -0,0 +1,150 @@
1
+ module Minimapper
2
+ EntityNotFound = Class.new(StandardError)
3
+
4
+ class Mapper
5
+ attr_accessor :repository
6
+
7
+ # Create
8
+
9
+ def create(entity)
10
+ record = record_class.new
11
+
12
+ copy_attributes_to_record(record, entity)
13
+ validate_record_and_copy_errors_to_entity(record, entity)
14
+
15
+ if entity.valid?
16
+ record.save!
17
+ entity.id = record.id
18
+ entity.id
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ # Read
25
+
26
+ def find(id)
27
+ entity_for(find_record_safely(id))
28
+ end
29
+
30
+ def find_by_id(id)
31
+ entity_for(find_record(id))
32
+ end
33
+
34
+ def all
35
+ entities_for record_class.all
36
+ end
37
+
38
+ def first
39
+ entity_for(record_class.order("id ASC").first)
40
+ end
41
+
42
+ def last
43
+ entity_for(record_class.order("id ASC").last)
44
+ end
45
+
46
+ def count
47
+ record_class.count
48
+ end
49
+
50
+ # Update
51
+
52
+ def update(entity)
53
+ record = record_for(entity)
54
+
55
+ copy_attributes_to_record(record, entity)
56
+ validate_record_and_copy_errors_to_entity(record, entity)
57
+
58
+ if entity.valid?
59
+ record.save!
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end
65
+
66
+ # Delete
67
+
68
+ def delete(entity)
69
+ delete_by_id(entity.id)
70
+ entity.id = nil
71
+ end
72
+
73
+ def delete_by_id(id)
74
+ find_record_safely(id).delete
75
+ end
76
+
77
+ def delete_all
78
+ record_class.delete_all
79
+ end
80
+
81
+ private
82
+
83
+ # NOTE: Don't memoize the record_class or code reloading will break in rails apps.
84
+ def record_class
85
+ "#{self.class.name}::Record".constantize
86
+ end
87
+
88
+ # Will attempt to use Project as the entity class when
89
+ # the mapper class name is ProjectMapper.
90
+ def entity_class
91
+ self.class.name.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 copy_attributes_to_record(record, entity)
103
+ record.attributes = accessible_attributes(entity)
104
+ end
105
+
106
+ def validate_record_and_copy_errors_to_entity(record, entity)
107
+ record.valid?
108
+ entity.mapper_errors = record.errors.map { |k, v| [k, v] }
109
+ end
110
+
111
+ def find_record_safely(id)
112
+ find_record(id) ||
113
+ raise(EntityNotFound, :id => id)
114
+ end
115
+
116
+ def find_record(id)
117
+ id && record_class.find_by_id(id)
118
+ end
119
+
120
+ def record_for(entity)
121
+ (entity.id && record_class.find_by_id(entity.id)) ||
122
+ raise(EntityNotFound, entity.inspect)
123
+ end
124
+
125
+ def entities_for(records)
126
+ records.map { |record| entity_for(record) }
127
+ end
128
+
129
+ def entity_for(record, klass = entity_class)
130
+ if record
131
+ entity = klass.new
132
+ entity.id = record.id
133
+ entity.attributes = record.attributes.symbolize_keys
134
+
135
+ if klass == entity_class
136
+ after_find(entity, record)
137
+ end
138
+
139
+ entity
140
+ else
141
+ nil
142
+ end
143
+ end
144
+
145
+ # Hooks
146
+
147
+ def after_find(entity, record)
148
+ end
149
+ end
150
+ end
@@ -1,3 +1,3 @@
1
1
  module Minimapper
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
data/minimapper.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Minimapper::VERSION
9
9
  gem.authors = ["Joakim Kolsjo"]
10
10
  gem.email = ["joakim.kolsjo@gmail.com"]
11
- gem.description = %q{A minimalistic way of separating your models from ORMs like ActiveRecord.}
12
- gem.summary = %q{A minimalistic way of separating your models from ORMs like ActiveRecord.}
11
+ gem.description = %q{A minimalistic way of separating your models from ActiveRecord.}
12
+ gem.summary = %q{A minimalistic way of separating your models from ActiveRecord.}
13
13
  gem.homepage = ""
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
@@ -1,22 +1,14 @@
1
1
  require "spec_helper"
2
2
  require "minimapper/entity/core"
3
- require "minimapper/mapper/ar"
3
+ require "minimapper/mapper"
4
4
 
5
- class TestEntity
5
+ class Project
6
6
  include Minimapper::Entity::Core
7
7
  end
8
8
 
9
- class TestMapper < Minimapper::Mapper::AR
9
+ class ProjectMapper < Minimapper::Mapper
10
10
  private
11
11
 
12
- def entity_class
13
- TestEntity
14
- end
15
-
16
- def record_class
17
- Record
18
- end
19
-
20
12
  class Record < ActiveRecord::Base
21
13
  attr_protected :visible
22
14
 
@@ -29,9 +21,9 @@ class TestMapper < Minimapper::Mapper::AR
29
21
  end
30
22
  end
31
23
 
32
- describe Minimapper::Mapper::AR do
33
- let(:mapper) { TestMapper.new }
34
- let(:entity_class) { TestEntity }
24
+ describe Minimapper::Mapper do
25
+ let(:mapper) { ProjectMapper.new }
26
+ let(:entity_class) { Project }
35
27
 
36
28
  include_examples :mapper
37
29
 
@@ -45,9 +37,9 @@ describe Minimapper::Mapper::AR do
45
37
  stored_entity.attributes[:visible].should be_nil
46
38
  stored_entity.attributes[:name].should == "Joe"
47
39
 
48
- entity = TestEntity.new
40
+ entity = Project.new
49
41
  entity.attributes = { :visible => true, :name => "Joe" }
50
- TestMapper::Record.stub(:protected_attributes => [])
42
+ ProjectMapper::Record.stub(:protected_attributes => [])
51
43
  lambda { mapper.create(entity) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
52
44
  end
53
45
 
@@ -75,7 +67,7 @@ describe Minimapper::Mapper::AR do
75
67
  end
76
68
 
77
69
  def build_entity(attributes)
78
- entity = TestEntity.new
70
+ entity = Project.new
79
71
  entity.attributes = attributes
80
72
  entity
81
73
  end
@@ -83,7 +75,7 @@ describe Minimapper::Mapper::AR do
83
75
 
84
76
  describe "#update" do
85
77
  it "does not include protected attributes" do
86
- entity = TestEntity.new
78
+ entity = Project.new
87
79
  mapper.create(entity)
88
80
 
89
81
  entity.attributes = { :visible => true, :name => "Joe" }
@@ -92,7 +84,7 @@ describe Minimapper::Mapper::AR do
92
84
  stored_entity.attributes[:visible].should be_nil
93
85
  stored_entity.attributes[:name].should == "Joe"
94
86
 
95
- TestMapper::Record.stub(:protected_attributes => [])
87
+ ProjectMapper::Record.stub(:protected_attributes => [])
96
88
  lambda { mapper.update(entity) }.should raise_error(ActiveModel::MassAssignmentSecurity::Error)
97
89
  end
98
90
 
@@ -100,7 +92,7 @@ describe Minimapper::Mapper::AR do
100
92
  old_entity = build_entity(:email => "joe@example.com")
101
93
  mapper.create(old_entity)
102
94
 
103
- new_entity = TestEntity.new
95
+ new_entity = Project.new
104
96
  mapper.create(new_entity)
105
97
  new_entity.mapper_errors.should == []
106
98
 
@@ -113,7 +105,7 @@ describe Minimapper::Mapper::AR do
113
105
  old_entity = build_entity(:email => "joe@example.com")
114
106
  mapper.create(old_entity)
115
107
 
116
- new_entity = TestEntity.new
108
+ new_entity = Project.new
117
109
  mapper.create(new_entity)
118
110
  new_entity.mapper_errors.should == []
119
111
 
@@ -127,7 +119,7 @@ describe Minimapper::Mapper::AR do
127
119
  end
128
120
 
129
121
  def build_entity(attributes)
130
- entity = TestEntity.new
122
+ entity = Project.new
131
123
  entity.attributes = attributes
132
124
  entity
133
125
  end
@@ -2,39 +2,46 @@ require 'minimapper/entity/convert'
2
2
  require 'active_support/core_ext'
3
3
 
4
4
  describe Minimapper::Entity::Convert do
5
- it "converts strings into integers" do
6
- described_class.new('10').to(:integer).should == 10
7
- described_class.new(' 10 ').to(:integer).should == 10
8
- end
5
+ describe ":integer" do
6
+ it "allows integers" do
7
+ described_class.new(10).to(:integer).should == 10
8
+ end
9
+
10
+ it "converts strings into integers" do
11
+ described_class.new('10').to(:integer).should == 10
12
+ described_class.new(' 10 ').to(:integer).should == 10
13
+ end
9
14
 
10
- it "converts datetime strings into datetimes" do
11
- described_class.new('2012-01-01 20:57').to(:date_time).should == DateTime.new(2012, 01, 01, 20, 57)
15
+ it "makes it nil when it can't convert" do
16
+ described_class.new(' ').to(:integer).should be_nil
17
+ described_class.new('garbage').to(:integer).should be_nil
18
+ end
12
19
  end
13
20
 
14
- it "makes it nil when it can't convert" do
15
- described_class.new(' ').to(:integer).should be_nil
16
- described_class.new(' ').to(:date_time).should be_nil
17
- described_class.new('garbage').to(:integer).should be_nil
18
- described_class.new('garbage').to(:date_time).should be_nil
21
+ describe ":date_time" do
22
+ it "allows DateTimes" do
23
+ described_class.new(DateTime.new(2013, 6, 1)).to(:date_time).should == DateTime.new(2013, 6, 1)
24
+ end
25
+
26
+ it "converts datetime strings into datetimes" do
27
+ described_class.new('2012-01-01 20:57').to(:date_time).should == DateTime.new(2012, 01, 01, 20, 57)
28
+ end
29
+
30
+ it "makes it nil when it can't convert" do
31
+ described_class.new(' ').to(:date_time).should be_nil
32
+ described_class.new('garbage').to(:date_time).should be_nil
33
+ end
19
34
  end
20
35
 
21
36
  it "returns the value as-is when the type isn't specified" do
22
37
  described_class.new('foobar').to(nil).should == 'foobar'
23
38
  end
24
39
 
25
- it "does not make false nil" do
26
- described_class.new(false).to(nil).should eq(false)
27
- end
28
-
29
40
  it "raises when the type isn't known" do
30
41
  lambda { described_class.new('foobar').to(:unknown) }.should raise_error(/Unknown attribute type/)
31
42
  end
32
43
 
33
44
  it "does not make false nil" do
34
- described_class.new(false).to(:unknown).should eq(false)
35
- end
36
-
37
- it "does not make false nil" do
38
- described_class.new(false).to(:unknown).should eq(false)
45
+ described_class.new(false).to(:whatever).should eq(false)
39
46
  end
40
47
  end
@@ -1,8 +1,8 @@
1
1
  require 'minimapper/repository'
2
- require 'minimapper/mapper/memory'
3
2
 
4
3
  module Test
5
- class ProjectMapper < Minimapper::Mapper::Memory
4
+ class ProjectMapper
5
+ attr_accessor :repository
6
6
  end
7
7
  end
8
8
 
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.6.2
4
+ version: 0.7.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-05-23 00:00:00.000000000 Z
12
+ date: 2013-06-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70133111671800 !ruby/object:Gem::Requirement
16
+ requirement: &70148053526900 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,8 +21,8 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70133111671800
25
- description: A minimalistic way of separating your models from ORMs like ActiveRecord.
24
+ version_requirements: *70148053526900
25
+ description: A minimalistic way of separating your models from ActiveRecord.
26
26
  email:
27
27
  - joakim.kolsjo@gmail.com
28
28
  executables: []
@@ -44,15 +44,13 @@ files:
44
44
  - lib/minimapper/entity/convert/to_integer.rb
45
45
  - lib/minimapper/entity/core.rb
46
46
  - lib/minimapper/entity/rails.rb
47
- - lib/minimapper/mapper/ar.rb
48
- - lib/minimapper/mapper/common.rb
49
- - lib/minimapper/mapper/memory.rb
47
+ - lib/minimapper/mapper.rb
50
48
  - lib/minimapper/repository.rb
51
49
  - lib/minimapper/version.rb
52
50
  - minimapper.gemspec
53
51
  - script/ci
54
52
  - script/turbux_rspec
55
- - spec/mapper/ar_spec.rb
53
+ - spec/mapper_spec.rb
56
54
  - spec/spec_helper.rb
57
55
  - spec/support/database_setup.rb
58
56
  - spec/support/shared_examples/mapper.rb
@@ -60,7 +58,6 @@ files:
60
58
  - unit/entity/core_spec.rb
61
59
  - unit/entity/rails_spec.rb
62
60
  - unit/entity_spec.rb
63
- - unit/mapper/memory_spec.rb
64
61
  - unit/repository_spec.rb
65
62
  - unit/spec_helper.rb
66
63
  homepage: ''
@@ -77,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
74
  version: '0'
78
75
  segments:
79
76
  - 0
80
- hash: -2264146464665909809
77
+ hash: -4244542897336928819
81
78
  required_rubygems_version: !ruby/object:Gem::Requirement
82
79
  none: false
83
80
  requirements:
@@ -86,15 +83,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
83
  version: '0'
87
84
  segments:
88
85
  - 0
89
- hash: -2264146464665909809
86
+ hash: -4244542897336928819
90
87
  requirements: []
91
88
  rubyforge_project:
92
89
  rubygems_version: 1.8.5
93
90
  signing_key:
94
91
  specification_version: 3
95
- summary: A minimalistic way of separating your models from ORMs like ActiveRecord.
92
+ summary: A minimalistic way of separating your models from ActiveRecord.
96
93
  test_files:
97
- - spec/mapper/ar_spec.rb
94
+ - spec/mapper_spec.rb
98
95
  - spec/spec_helper.rb
99
96
  - spec/support/database_setup.rb
100
97
  - spec/support/shared_examples/mapper.rb
@@ -1,155 +0,0 @@
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
-
13
- copy_attributes_to_record(record, entity)
14
- validate_record_and_copy_errors_to_entity(record, entity)
15
-
16
- if entity.valid?
17
- record.save!
18
- entity.id = record.id
19
- entity.id
20
- else
21
- false
22
- end
23
- end
24
-
25
- # Read
26
-
27
- def find(id)
28
- entity_for(find_record_safely(id))
29
- end
30
-
31
- def find_by_id(id)
32
- entity_for(find_record(id))
33
- end
34
-
35
- def all
36
- entities_for record_class.all
37
- end
38
-
39
- def first
40
- entity_for(record_class.order("id ASC").first)
41
- end
42
-
43
- def last
44
- entity_for(record_class.order("id ASC").last)
45
- end
46
-
47
- def count
48
- record_class.count
49
- end
50
-
51
- # Update
52
-
53
- def update(entity)
54
- record = record_for(entity)
55
-
56
- copy_attributes_to_record(record, entity)
57
- validate_record_and_copy_errors_to_entity(record, entity)
58
-
59
- if entity.valid?
60
- record.save!
61
- true
62
- else
63
- false
64
- end
65
- end
66
-
67
- # Delete
68
-
69
- def delete(entity)
70
- delete_by_id(entity.id)
71
- entity.id = nil
72
- end
73
-
74
- def delete_by_id(id)
75
- find_record_safely(id).delete
76
- end
77
-
78
- def delete_all
79
- record_class.delete_all
80
- end
81
-
82
- private
83
-
84
- # NOTE: Don't memoize the classes or code reloading will break in rails apps.
85
-
86
- # Will attempt to use AR:Project as the record class
87
- # when the mapper class name is AR::ProjectMapper.
88
- def record_class
89
- self.class.name.sub(/Mapper$/, '').constantize
90
- end
91
-
92
- # Will attempt to use Project as the entity class when
93
- # the mapper class name is AR::ProjectMapper.
94
- def entity_class
95
- ("::" + self.class.name.split('::').last.sub(/Mapper$/, '')).constantize
96
- end
97
-
98
- def accessible_attributes(entity)
99
- entity.attributes.reject { |k, v| protected_attributes.include?(k.to_s) }
100
- end
101
-
102
- def protected_attributes
103
- record_class.protected_attributes
104
- end
105
-
106
- def copy_attributes_to_record(record, entity)
107
- record.attributes = accessible_attributes(entity)
108
- end
109
-
110
- def validate_record_and_copy_errors_to_entity(record, entity)
111
- record.valid?
112
- entity.mapper_errors = record.errors.map { |k, v| [k, v] }
113
- end
114
-
115
- def find_record_safely(id)
116
- find_record(id) ||
117
- raise(EntityNotFound, :id => id)
118
- end
119
-
120
- def find_record(id)
121
- id && record_class.find_by_id(id)
122
- end
123
-
124
- def record_for(entity)
125
- (entity.id && record_class.find_by_id(entity.id)) ||
126
- raise(EntityNotFound, entity.inspect)
127
- end
128
-
129
- def entities_for(records)
130
- records.map { |record| entity_for(record) }
131
- end
132
-
133
- def entity_for(record, klass = entity_class)
134
- if record
135
- entity = klass.new
136
- entity.id = record.id
137
- entity.attributes = record.attributes.symbolize_keys
138
-
139
- if klass == entity_class
140
- after_find(entity, record)
141
- end
142
-
143
- entity
144
- else
145
- nil
146
- end
147
- end
148
-
149
- # Hooks
150
-
151
- def after_find(entity, record)
152
- end
153
- end
154
- end
155
- end
@@ -1,9 +0,0 @@
1
- module Minimapper
2
- EntityNotFound = Class.new(StandardError)
3
-
4
- module Mapper
5
- module Common
6
- attr_accessor :repository
7
- end
8
- end
9
- end
@@ -1,101 +0,0 @@
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(EntityNotFound, :id => id)
81
- end
82
-
83
- def find_internal(id)
84
- entity = id && store.find { |e| e.id == id.to_i }
85
- after_find(entity)
86
- entity
87
- end
88
-
89
- def next_id
90
- @last_id += 1
91
- end
92
-
93
- attr_reader :store, :last_id
94
-
95
- # Hooks
96
-
97
- def after_find(entity)
98
- end
99
- end
100
- end
101
- end
@@ -1,13 +0,0 @@
1
- require 'minimapper/mapper/memory'
2
- require 'minimapper/entity/core'
3
-
4
- class BasicEntity
5
- include Minimapper::Entity::Core
6
- end
7
-
8
- describe Minimapper::Mapper::Memory do
9
- let(:mapper) { described_class.new }
10
- let(:entity_class) { BasicEntity }
11
-
12
- include_examples :mapper
13
- end