ixtlan-optimistic 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # Ixtlan Optimistic [![Build Status](https://secure.travis-ci.org/mkristian/ixtlan-optimistic.png)](http://travis-ci.org/mkristian/ixtlan-optimistic) #
1
+ # Ixtlan Optimistic #
2
2
 
3
- it adds optimistic persistence support to DataMapper and ActveRecord usingg the updated_at property/attribute which is automatically updated on any change of the model (for datamapper you need dm-timestamps for that). to load a model use `optimistic_get`/`optimistic_get!`/`optimistic_find` respectively where the first argument is the last `updated_at` value which the client has. if the client data is uptodate then the `optimistic_XYZ` method will return the database entity otherwise raise an exception or return nil respectively.
3
+ * [![Build Status](https://secure.travis-ci.org/mkristian/ixtlan-optimistic.png)](http://travis-ci.org/mkristian/ixtlan-optimistic)
4
+ * [![Dependency Status](https://gemnasium.com/mkristian/ixtlan-optimistic.png)](https://gemnasium.com/mkristian/ixtlan-optimistic)
5
+ * [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/mkristian/ixtlan-optimistic)
6
+
7
+
8
+
9
+ it adds optimistic persistence support to DataMapper and ActveRecord using the updated\_at property/attribute which is automatically updated on any change of the model (for datamapper you need dm-timestamps for that). to load a model use `optimistic_get`/`optimistic_get!`/`optimistic_find` respectively where the first argument is the last `updated_at` value which the client has. if the client data is uptodate then the `optimistic_XYZ` method will return the database entity otherwise raise an exception or return nil respectively.
4
10
 
5
11
  ## rails setup ##
6
12
 
@@ -33,6 +39,16 @@ just add it with
33
39
  ::ActiveRecord::Base.send(:include,
34
40
  Ixtlan::Optimistic::ActiveRecord)
35
41
 
36
- # meta-fu #
42
+ Contributing
43
+ ------------
44
+
45
+ 1. Fork it
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create new Pull Request
50
+
51
+ meta-fu
52
+ -------
37
53
 
38
- bug-reports, feature request and pull request are most welcome.
54
+ enjoy :)
@@ -1,3 +1,23 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
1
21
  if defined?(Rails)
2
22
  require 'ixtlan/optimistic/railtie'
3
23
  end
@@ -1,28 +1,39 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
1
21
  require 'ixtlan/optimistic/object_stale_exception'
22
+ require 'ixtlan/optimistic/stale_check'
2
23
  module Ixtlan
3
24
  module Optimistic
4
25
  module ActiveRecord
5
26
 
6
27
  def self.included(base)
28
+ base.extend StaleCheck
7
29
  base.class_eval do
8
-
9
30
  def self.optimistic_find(updated_at, *args)
10
- if updated_at
11
- dummy = self.new
12
- dummy.updated_at = updated_at
13
- updated_at_date = dummy.updated_at
14
- # try different ways to use the date
15
- # TODO maybe there is a nicer way ??
16
- # TODO make it work with different PKs
17
- result = first(:conditions => ["id = ? and updated_at <= ? and updated_at >= ?", args[0], updated_at_date + 0.0005, updated_at_date - 0.0005])
18
- p result
19
- raise ObjectStaleException.new "#{self.class} with ID=#{args[0]} is stale" unless result
20
- result
21
- else
22
- raise ObjectStaleException.new "no 'updated_at' given. could not dind #{self.class} with ID=#{args[0]}."
31
+ __check( updated_at )
32
+ result = find( *args )
33
+ if result
34
+ __check_stale( updated_at, result )
23
35
  end
24
36
  end
25
-
26
37
  end
27
38
  end
28
39
  end
@@ -1,32 +1,48 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
1
21
  require 'ixtlan/optimistic/object_stale_exception'
22
+ require 'ixtlan/optimistic/stale_check'
2
23
  module Ixtlan
3
24
  module Optimistic
4
25
  module DataMapper
5
26
 
6
27
  def self.included(base)
28
+ base.extend StaleCheck
7
29
  base.class_eval do
8
-
9
- def self.optimistic_get(updated_at, *args)
10
- if updated_at
11
- updated_at_date = new(:updated_at => updated_at).updated_at
12
- # TODO make it work with different PKs
13
- first(:id => args[0], :updated_at.gte => updated_at_date - 0.0005, :updated_at.lte => updated_at_date + 0.0005)
30
+
31
+ def self.optimistic_get(updated_at, *args)
32
+ __check( updated_at )
33
+ result = get( *args )
34
+ if result
35
+ __check_stale( updated_at, result )
14
36
  end
15
37
  end
16
-
38
+
17
39
  def self.optimistic_get!(updated_at, *args)
18
- if updated_at
19
- result = self.optimistic_get(updated_at, *args)
20
- raise ObjectStaleException.new "#{self.class} with ID=#{args[0]} is stale" unless result
21
- result
22
- else
23
- raise ObjectStaleException.new "no 'updated_at' given. could not dind #{self.class} with ID=#{args[0]}."
24
- end
40
+ __check( updated_at )
41
+ result = get!( *args )
42
+ __check_stale( updated_at, result )
25
43
  end
26
44
  end
27
-
28
45
  end
29
-
30
46
  end
31
47
  end
32
48
  end
@@ -1,3 +1,23 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
1
21
  module Ixtlan
2
22
  module Optimistic
3
23
  class ObjectStaleException < StandardError
@@ -1,3 +1,23 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
1
21
  require 'ixtlan/optimistic/active_record'
2
22
  require 'ixtlan/optimistic/data_mapper'
3
23
  module Ixtlan
@@ -18,4 +38,4 @@ module Ixtlan
18
38
  end
19
39
  end
20
40
  end
21
- end
41
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'ixtlan/optimistic/object_stale_exception'
22
+ module Ixtlan
23
+ module Optimistic
24
+ module StaleCheck
25
+
26
+ def __check( updated_at )
27
+ unless updated_at
28
+ raise ObjectStaleException.new "no 'updated_at' given. could not find #{signature(*args)}."
29
+ end
30
+ end
31
+
32
+ def __check_stale( updated_at, result )
33
+ if updated_at.is_a?( String )
34
+ updated_at = DateTime.parse( updated_at.sub(/[.][0-9]+/, '') )
35
+ end
36
+ updated_at = updated_at.new_offset(0)
37
+ if updated_at != result.updated_at && updated_at.strftime("%Y:%m:%d %H:%M:%S") != result.updated_at.new_offset(0).strftime("%Y:%m:%d %H:%M:%S")
38
+
39
+ raise ObjectStaleException.new "#{result.inspect} is stale for updated at #{updated_at}."
40
+ end
41
+ result
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
1
+ #
2
+ # Copyright (C) 2012 mkristian
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ # this software and associated documentation files (the "Software"), to deal in
6
+ # the Software without restriction, including without limitation the rights to
7
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ # the Software, and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all
12
+ # copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ #
21
+ require 'ixtlan/optimistic/object_stale_exception'
22
+ module Ixtlan
23
+ module Optimistic
24
+ module DataMapper
25
+
26
+ def self.included(base)
27
+ base.class_eval do
28
+ private
29
+
30
+ def self.__check( updated_at )
31
+ unless updated_at
32
+ raise ObjectStaleException.new "no 'updated_at' given. could not find #{signature(*args)}."
33
+ end
34
+ end
35
+
36
+ def self.__check_stale( updated_at, result )
37
+ if updated_at.is_a?( String )
38
+ updated_at = DateTime.parse( updated_at.sub(/[.][0-9]+/, '') )
39
+ end
40
+ if updated_at != result.updated_at && updated_at.strftime("%Y:%m:%d %H:%M:%S") != result.updated_at.strftime("%Y:%m:%d %H:%M:%S")
41
+
42
+ raise ObjectStaleException.new "#{self.class} with key #{result.key} is stale for updated at #{updated_at}."
43
+ end
44
+ result
45
+ end
46
+
47
+ public
48
+
49
+ def self.optimistic_get(updated_at, *args)
50
+ __check( updated_at )
51
+ result = get( *args )
52
+ if result
53
+ __check_stale( updated_at, result )
54
+ end
55
+ end
56
+
57
+ def self.optimistic_get!(updated_at, *args)
58
+ __check( updated_at )
59
+ result = get!( *args )
60
+ __check_stale( updated_at, result )
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -23,16 +23,36 @@ describe Ixtlan::Optimistic::DataMapper do
23
23
 
24
24
  subject { A.create :name => 'huffalump' }
25
25
 
26
- it 'should load' do
27
- A.optimistic_get!(subject.updated_at.to_s, subject.id).must_equal subject
28
- end
26
+ describe "#optimistic_get" do
27
+
28
+ it 'should load' do
29
+ A.optimistic_get(subject.updated_at.to_s, subject.id).must_equal subject
30
+ end
31
+
32
+ it 'should fail with stale exception' do
33
+ lambda { A.optimistic_get((subject.updated_at - 1000).to_s, subject.id) }.must_raise Ixtlan::Optimistic::ObjectStaleException
34
+ end
35
+
36
+ it 'should fail with nil' do
37
+ A.optimistic_get(subject.updated_at.to_s, subject.id + 987).must_be_nil
38
+ end
29
39
 
30
- it 'should fail with nil' do
31
- A.optimistic_get((subject.updated_at - 1000).to_s, subject.id).must_be_nil
32
40
  end
33
41
 
34
- it 'should fail with exception' do
35
- lambda { A.optimistic_get!((subject.updated_at - 1000).to_s, subject.id) }.must_raise Ixtlan::Optimistic::ObjectStaleException
42
+ describe "#optimistic_get!" do
43
+
44
+ it 'should load' do
45
+ A.optimistic_get!(subject.updated_at.to_s, subject.id).must_equal subject
46
+ end
47
+
48
+ it 'should fail with not-found exception' do
49
+ lambda { A.optimistic_get!(subject.updated_at.to_s, subject.id + 987) }.must_raise DataMapper::ObjectNotFoundError
50
+ end
51
+
52
+ it 'should fail with stale exception' do
53
+ lambda { A.optimistic_get!((subject.updated_at - 1000).to_s, subject.id) }.must_raise Ixtlan::Optimistic::ObjectStaleException
54
+ end
55
+
36
56
  end
37
57
 
38
58
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ixtlan-optimistic
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - mkristian
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-07-07 00:00:00 Z
18
+ date: 2012-12-29 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: minitest
@@ -23,14 +23,14 @@ dependencies:
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
- - - "="
26
+ - - ~>
27
27
  - !ruby/object:Gem::Version
28
- hash: 15
28
+ hash: 51
29
29
  segments:
30
+ - 4
30
31
  - 3
31
- - 2
32
32
  - 0
33
- version: 3.2.0
33
+ version: 4.3.0
34
34
  type: :development
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
@@ -87,17 +87,31 @@ dependencies:
87
87
  requirement: &id005 !ruby/object:Gem::Requirement
88
88
  none: false
89
89
  requirements:
90
- - - "="
90
+ - - ~>
91
91
  - !ruby/object:Gem::Version
92
- hash: 11
92
+ hash: 75
93
93
  segments:
94
+ - 10
94
95
  - 0
95
- - 9
96
96
  - 2
97
- - 2
98
- version: 0.9.2.2
97
+ version: 10.0.2
99
98
  type: :development
100
99
  version_requirements: *id005
100
+ - !ruby/object:Gem::Dependency
101
+ name: copyright-header
102
+ prerelease: false
103
+ requirement: &id006 !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ hash: 15
109
+ segments:
110
+ - 1
111
+ - 0
112
+ version: "1.0"
113
+ type: :development
114
+ version_requirements: *id006
101
115
  description: optimistic find/get on model via updated_at timestamp for datamapper and activerecord
102
116
  email:
103
117
  - m.kristian@web.de
@@ -112,14 +126,16 @@ files:
112
126
  - README.md
113
127
  - lib/ixtlan-optimistic.rb
114
128
  - lib/ixtlan/optimistic/active_record.rb
129
+ - lib/ixtlan/optimistic/stale_check.rb~
115
130
  - lib/ixtlan/optimistic/data_mapper.rb
131
+ - lib/ixtlan/optimistic/stale_check.rb
116
132
  - lib/ixtlan/optimistic/railtie.rb
117
133
  - lib/ixtlan/optimistic/object_stale_exception.rb
118
134
  - spec/datamapper_spec.rb~
119
135
  - spec/datamapper_spec.rb
120
136
  homepage: http://github.com/mkristian/ixtlan-optimistic
121
137
  licenses:
122
- - MIT-LICENSE
138
+ - MIT
123
139
  post_install_message:
124
140
  rdoc_options:
125
141
  - --main
@@ -147,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
163
  requirements: []
148
164
 
149
165
  rubyforge_project:
150
- rubygems_version: 1.8.21
166
+ rubygems_version: 1.8.15
151
167
  signing_key:
152
168
  specification_version: 3
153
169
  summary: optimistic find/get on model via updated_at timestamp for datamapper and activerecord