datamappify 0.30.0 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/ERD.png +0 -0
  4. data/README.md +78 -16
  5. data/datamappify.gemspec +4 -3
  6. data/lib/datamappify/data/criteria/active_record/destroy.rb +1 -1
  7. data/lib/datamappify/data/criteria/active_record/exists.rb +2 -2
  8. data/lib/datamappify/data/criteria/active_record/transaction.rb +1 -1
  9. data/lib/datamappify/data/criteria/common.rb +21 -1
  10. data/lib/datamappify/data/criteria/relational/count.rb +1 -1
  11. data/lib/datamappify/data/criteria/relational/find.rb +1 -1
  12. data/lib/datamappify/data/criteria/relational/save.rb +1 -1
  13. data/lib/datamappify/data/criteria/sequel/destroy.rb +1 -1
  14. data/lib/datamappify/data/criteria/sequel/exists.rb +1 -1
  15. data/lib/datamappify/data/criteria/sequel/transaction.rb +1 -1
  16. data/lib/datamappify/data/mapper/attribute.rb +9 -0
  17. data/lib/datamappify/data/mapper.rb +7 -2
  18. data/lib/datamappify/data/provider/common_provider.rb +1 -1
  19. data/lib/datamappify/entity/lazy_checking.rb +12 -0
  20. data/lib/datamappify/entity.rb +4 -0
  21. data/lib/datamappify/lazy/attributes_handler.rb +123 -0
  22. data/lib/datamappify/lazy/source_attributes_walker.rb +49 -0
  23. data/lib/datamappify/lazy.rb +24 -0
  24. data/lib/datamappify/logger.rb +13 -0
  25. data/lib/datamappify/repository/lazy_checking.rb +19 -0
  26. data/lib/datamappify/repository/mapping_dsl.rb +7 -1
  27. data/lib/datamappify/repository/query_method/callbacks.rb +83 -0
  28. data/lib/datamappify/repository/query_method/count.rb +6 -1
  29. data/lib/datamappify/repository/query_method/create.rb +10 -0
  30. data/lib/datamappify/repository/query_method/destroy.rb +6 -14
  31. data/lib/datamappify/repository/query_method/exists.rb +17 -0
  32. data/lib/datamappify/repository/query_method/find.rb +12 -19
  33. data/lib/datamappify/repository/query_method/method/source_attributes_walker.rb +81 -0
  34. data/lib/datamappify/repository/query_method/method.rb +74 -25
  35. data/lib/datamappify/repository/query_method/save.rb +15 -14
  36. data/lib/datamappify/repository/query_method/update.rb +10 -0
  37. data/lib/datamappify/repository/query_methods.rb +123 -0
  38. data/lib/datamappify/repository/unit_of_work/persistent_states/object.rb +122 -0
  39. data/lib/datamappify/repository/unit_of_work/persistent_states.rb +54 -0
  40. data/lib/datamappify/repository/unit_of_work/transaction.rb +18 -0
  41. data/lib/datamappify/repository/unit_of_work.rb +1 -0
  42. data/lib/datamappify/repository.rb +16 -51
  43. data/lib/datamappify/version.rb +1 -1
  44. data/lib/datamappify.rb +3 -1
  45. data/spec/lazy_spec.rb +73 -0
  46. data/spec/repository/callbacks_spec.rb +140 -0
  47. data/spec/repository/dirty_persistence_spec.rb +44 -0
  48. data/spec/repository/dirty_tracking_spec.rb +82 -0
  49. data/spec/repository/persistence_spec.rb +41 -119
  50. data/spec/repository/transactions_spec.rb +25 -0
  51. data/spec/repository/validation_spec.rb +42 -0
  52. data/spec/repository_spec.rb +8 -6
  53. data/spec/spec_helper.rb +2 -2
  54. data/spec/support/entities/hero_user.rb +5 -0
  55. data/spec/support/repositories/callbacks_chaining_repository.rb +92 -0
  56. data/spec/support/repositories/hero_user_repository.rb +30 -0
  57. data/spec/support/shared/contexts.rb +10 -0
  58. data/spec/support/tables/sequel.rb +1 -0
  59. data/spec/unit/repository/query_method_spec.rb +55 -0
  60. metadata +57 -10
  61. data/lib/datamappify/repository/query_method/transaction.rb +0 -18
  62. data/lib/datamappify/repository/query_method.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: afcba7b889daac95e25ea353220e7f418082515d
4
- data.tar.gz: d0cd4d8e8a01595ffe831c204ad52cccdafef85f
3
+ metadata.gz: 425218c5431f6668819ee6dee7cec37c8aa9e23f
4
+ data.tar.gz: a928d202dd9750197c297721fcb7142279a52d42
5
5
  SHA512:
6
- metadata.gz: d95340abf96122fc88f136f028d31b0bf5b15915bbab6bc72e8a9180202ac8220e723c0b39f55b24b223640231580cbf8ae17636c118b86530bd9c81c0fceb40
7
- data.tar.gz: e90bcfb79e7f232bcab801f73326d3088891454aa93db4cf6060590fc2fafc3f5bbf0bb8cd172aa3b017ab8677442db95a369c3d7615852123eded0c98114b72
6
+ metadata.gz: 250ba0d6203d7a76ee0deab3ed581783048960851f280821be7670507b4cee18115d5593f97c74a790a628b5c342e4ac9bba6a63b9997bfba2ae1a76a94857cc
7
+ data.tar.gz: aa64bb2f335e6e480c8ae2b0f3dbb70a70d5a45f924920238ac416a19550e54f0d7e652cb3118769c8af3f351329a4365a6accd7ab2c522c5c8f1cfaaca0d2bf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## master
2
2
 
3
+ ## 0.40.0 [2013-05-21]
4
+
5
+ - Added dirty attribute tracking.
6
+ - Implemented attribute lazy loading!
7
+ - Query methods (`find`, `save` and `destroy`) no longer support multiple entities.
8
+ - e.g. `UserRepository.find([1, 2, 3])` is no longer valid.
9
+ - Added `exists?`, `create`, `create!`, `update` and `update!` to repository.
10
+ - Implemented callbacks (e.g. `before_save` and `after_save`, etc).
11
+
3
12
  ## 0.30.0 [2013-04-12]
4
13
 
5
14
  - __Completely reimplemented Datamappify__
data/ERD.png ADDED
Binary file
data/README.md CHANGED
@@ -1,6 +1,4 @@
1
- # Datamappify [![Gem Version](https://badge.fury.io/rb/datamappify.png)](http://badge.fury.io/rb/datamappify) [![Build Status](https://api.travis-ci.org/fredwu/datamappify.png)](http://travis-ci.org/fredwu/datamappify) [![Coverage Status](https://coveralls.io/repos/fredwu/datamappify/badge.png)](https://coveralls.io/r/fredwu/datamappify) [![Code Climate](https://codeclimate.com/github/fredwu/datamappify.png)](https://codeclimate.com/github/fredwu/datamappify)
2
-
3
- __Datamappify is current in Proof-of-Concept stage, do NOT use it for anything other than experimentation.__
1
+ # Datamappify [![Gem Version](https://badge.fury.io/rb/datamappify.png)](http://badge.fury.io/rb/datamappify) [![Build Status](https://api.travis-ci.org/fredwu/datamappify.png?branch=master)](http://travis-ci.org/fredwu/datamappify) [![Coverage Status](https://coveralls.io/repos/fredwu/datamappify/badge.png)](https://coveralls.io/r/fredwu/datamappify) [![Code Climate](https://codeclimate.com/github/fredwu/datamappify.png)](https://codeclimate.com/github/fredwu/datamappify)
4
2
 
5
3
  ## Overview
6
4
 
@@ -8,15 +6,21 @@ Compose and manage domain logic and data persistence separately and intelligentl
8
6
 
9
7
  Datamappify is built using [Virtus](https://github.com/solnic/virtus) and existing ORMs (ActiveRecord and Sequel, etc). The design goal is to utilise the powerfulness of existing ORMs as well as to separate domain logic (model behaviour) from data persistence.
10
8
 
9
+ My motivation for creating Datamappify is to hide the complexity of dealing with data in different data sources including the ones from external web services. Features like lazy loading and dirty tracking are designed to enhance the usability of dealing with web services.
10
+
11
11
  Datamappify consists of three components:
12
12
 
13
13
  - __Entity__ contains models behaviour, think an ActiveRecord model with the persistence specifics removed.
14
- - __Data__ as the name suggests, holds your model data. It is an ORM object (ActiveRecord and Sequel, etc).
15
14
  - __Repository__ is responsible for data retrieval and persistence, e.g. `find`, `save` and `destroy`, etc.
15
+ - __Data__ as the name suggests, holds your model data. It contains ORM objects (ActiveRecord and Sequel, etc).
16
+
17
+ ![](ERD.png)
16
18
 
17
19
  Note: Datamappify is NOT affiliated with the [Datamapper](https://github.com/datamapper/) project.
18
20
 
19
- ### Supported ORMs for Persistence
21
+ ### Built-in ORMs for Persistence
22
+
23
+ You may implement your own [data provider and criterias](lib/datamappify/data), but Datamappify comes with build-in support for the following ORMS:
20
24
 
21
25
  - ActiveRecord
22
26
  - Sequel
@@ -55,6 +59,19 @@ class User
55
59
  end
56
60
  ```
57
61
 
62
+ #### Lazy loading
63
+
64
+ Datamappify supports attribute lazy loading via the `Lazy` module.
65
+
66
+ ```ruby
67
+ class User
68
+ include Datamappify::Entity
69
+ include Datamappify::Lazy
70
+ end
71
+ ```
72
+
73
+ When an entity is lazy loaded, only attributes from the default source will be loaded. Other attributes will only be loaded once they are called. This is especially useful if some of your data sources are external services.
74
+
58
75
  ### Repository
59
76
 
60
77
  Map entity attributes to DB columns - better yet, you can even map attributes to __different ORMs__!
@@ -85,48 +102,93 @@ end
85
102
 
86
103
  #### Retrieving entities
87
104
 
88
- Pass in an id or an array of ids.
105
+ Pass in an id.
89
106
 
90
107
  ```ruby
91
108
  user = UserRepository.find(1)
92
- users = UserRepository.find([1, 2, 3])
109
+ ```
110
+
111
+ #### Checking if an entity exists in the repository
112
+
113
+ Pass in an entity.
114
+
115
+ ```ruby
116
+ UserRepository.exists?(user)
93
117
  ```
94
118
 
95
119
  #### Saving/updating entities
96
120
 
97
- Pass in an entity or an array of entities.
121
+ Pass in an entity.
98
122
 
99
123
  There is also `save!` that raises `Datamappify::Data::EntityNotSaved`.
100
124
 
101
125
  ```ruby
102
126
  UserRepository.save(user)
103
- UserRepository.save([user, user2, user3])
104
127
  ```
105
128
 
129
+ Datamappify supports attribute dirty tracking - only dirty attributes will be saved.
130
+
106
131
  #### Destroying an entity
107
132
 
108
- Pass in an entity, an id, an array of entities or an array of ids.
133
+ Pass in an entity.
109
134
 
110
135
  There is also `destroy!` that raises `Datamappify::Data::EntityNotDestroyed`.
111
136
 
112
137
  Note that due to the attributes mapping, any data found in mapped ActiveRecord objects are not touched.
113
138
 
114
139
  ```ruby
115
- UserRepository.destroy(1)
116
- UserRepository.destroy([1, 2, 3])
117
140
  UserRepository.destroy(user)
118
- UserRepository.destroy([user, user2, user3])
119
141
  ```
120
142
 
143
+ #### Callbacks
144
+
145
+ Datamappify supports the following callbacks via [Hooks](https://github.com/apotonick/hooks):
146
+
147
+ - before_create
148
+ - before_update
149
+ - before_save
150
+ - before_destroy
151
+ - after_create
152
+ - after_update
153
+ - after_save
154
+ - after_destroy
155
+
156
+ Callbacks are defined in repositories, and they have access to the entity. Example:
157
+
158
+ ```ruby
159
+ class UserRepository
160
+ include Datamappify::Repository
161
+
162
+ before_create :make_me_admin
163
+ before_create :make_me_awesome
164
+ after_save :make_me_smile
165
+
166
+ private
167
+
168
+ def make_me_admin(entity)
169
+ # ...
170
+ end
171
+
172
+ def make_me_awesome(entity)
173
+ # ...
174
+ end
175
+
176
+ def make_me_smile(entity)
177
+ # ...
178
+ end
179
+
180
+ # ...
181
+ end
182
+ ```
183
+
184
+ Note: Returning either `nil` or `false` from the callback will cancel all subsequent callbacks (and the action itself, it it's a `before_` callback).
185
+
121
186
  ## Changelog
122
187
 
123
188
  Refer to [CHANGELOG](CHANGELOG.md).
124
189
 
125
190
  ## Todo
126
191
 
127
- - Track dirty entity attributes.
128
- - Attribute lazy-loading.
129
- - Hooks for persistence (`before_save` and `after_save`, etc).
130
192
  - [Authoritative source](http://msdn.microsoft.com/en-au/library/ff649505.aspx).
131
193
  - Enforce attribute type casting.
132
194
  - Support for configurable primary keys and foreign keys.
data/datamappify.gemspec CHANGED
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "virtus", "~> 0.5"
22
- spec.add_dependency "activesupport", ">= 4.0.0.beta1", "< 5"
21
+ spec.add_dependency "virtus", "~> 0.5"
22
+ spec.add_dependency "activemodel", ">= 4.0.0.rc1", "< 5"
23
+ spec.add_dependency "hooks", "~> 0.2.2"
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.3"
25
26
  spec.add_development_dependency "rake"
@@ -32,6 +33,6 @@ Gem::Specification.new do |spec|
32
33
  spec.add_development_dependency "coveralls"
33
34
  spec.add_development_dependency "sqlite3"
34
35
  spec.add_development_dependency "sequel"
35
- spec.add_development_dependency "activerecord", ">= 4.0.0.beta1", "< 5"
36
+ spec.add_development_dependency "activerecord", ">= 4.0.0.rc1", "< 5"
36
37
  spec.add_development_dependency "database_cleaner", ">= 1.0.0.RC1", "< 2"
37
38
  end
@@ -7,7 +7,7 @@ module Datamappify
7
7
  super(source_class, nil, id)
8
8
  end
9
9
 
10
- def result
10
+ def perform
11
11
  source_class.destroy(criteria)
12
12
  end
13
13
  end
@@ -3,8 +3,8 @@ module Datamappify
3
3
  module Criteria
4
4
  module ActiveRecord
5
5
  class Exists < Common
6
- def result
7
- source_class.exists?(entity.id)
6
+ def perform
7
+ !!source_class.exists?(entity.id)
8
8
  end
9
9
  end
10
10
  end
@@ -3,7 +3,7 @@ module Datamappify
3
3
  module Criteria
4
4
  module ActiveRecord
5
5
  class Transaction < Common
6
- def result
6
+ def perform
7
7
  source_class.transaction(&@block)
8
8
  end
9
9
  end
@@ -13,7 +13,7 @@ module Datamappify
13
13
  # @return [void]
14
14
  attr_reader :criteria
15
15
 
16
- # @return [Set]
16
+ # @return [Set<Mapper::Attribute>]
17
17
  attr_reader :attributes
18
18
 
19
19
  # @param source_class [Class]
@@ -28,6 +28,17 @@ module Datamappify
28
28
  @block = block
29
29
  end
30
30
 
31
+ # Performs the action (defined by child method classes) with callbacks
32
+ #
33
+ # @return [void]
34
+ def perform_with_callbacks
35
+ result = perform
36
+
37
+ store_attribute_value if attributes
38
+
39
+ result
40
+ end
41
+
31
42
  protected
32
43
 
33
44
  # Key name of either the primary key (e.g. +id+) or foreign key (e.g. +user_id+)
@@ -80,6 +91,15 @@ module Datamappify
80
91
  hash
81
92
  end
82
93
 
94
+ # Stores the attribute value in {Mapper::Attribute} for later use
95
+ #
96
+ # @return [void]
97
+ def store_attribute_value
98
+ attributes.each do |attribute|
99
+ attribute.value = entity.instance_variable_get("@#{attribute.name}")
100
+ end
101
+ end
102
+
83
103
  private
84
104
 
85
105
  # Ignores the attribute if it isn't dirty or if it's a primary key
@@ -3,7 +3,7 @@ module Datamappify
3
3
  module Criteria
4
4
  module Relational
5
5
  class Count < Common
6
- def result
6
+ def perform
7
7
  source_class.count
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Datamappify
3
3
  module Criteria
4
4
  module Relational
5
5
  class Find < Common
6
- def result
6
+ def perform
7
7
  record = source_class.where(criteria).first
8
8
 
9
9
  update_entity_with(record) if record
@@ -3,7 +3,7 @@ module Datamappify
3
3
  module Criteria
4
4
  module Relational
5
5
  class Save < Common
6
- def result
6
+ def perform
7
7
  new_record? ? create_record : save_record unless ignore?
8
8
  end
9
9
 
@@ -7,7 +7,7 @@ module Datamappify
7
7
  super(source_class, nil, id)
8
8
  end
9
9
 
10
- def result
10
+ def perform
11
11
  source_class.where(:id => criteria).destroy
12
12
  end
13
13
  end
@@ -3,7 +3,7 @@ module Datamappify
3
3
  module Criteria
4
4
  module Sequel
5
5
  class Exists < Common
6
- def result
6
+ def perform
7
7
  source_class.where(:id => entity.id).any?
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@ module Datamappify
3
3
  module Criteria
4
4
  module Sequel
5
5
  class Transaction < Common
6
- def result
6
+ def perform
7
7
  source_class.db.transaction(&@block)
8
8
  end
9
9
  end
@@ -3,6 +3,11 @@ module Datamappify
3
3
  class Mapper
4
4
  # Represents an entity attribute and its associated data source
5
5
  class Attribute
6
+ # Same as name, but in symbol
7
+ #
8
+ # @return [Symbol]
9
+ attr_reader :key
10
+
6
11
  # @return [String]
7
12
  attr_reader :name
8
13
 
@@ -15,6 +20,9 @@ module Datamappify
15
20
  # @return [String]
16
21
  attr_reader :source_attribute_name
17
22
 
23
+ # @return [any]
24
+ attr_accessor :value
25
+
18
26
  # @param name [Symbol]
19
27
  # name of the attribute
20
28
  #
@@ -22,6 +30,7 @@ module Datamappify
22
30
  # data provider, class and attribute,
23
31
  # e.g. "ActiveRecord::User#surname"
24
32
  def initialize(name, source)
33
+ @key = name
25
34
  @name = name.to_s
26
35
 
27
36
  @provider_name, @source_class_name, @source_attribute_name = parse_source(source)
@@ -34,10 +34,15 @@ module Datamappify
34
34
  @default_source_class ||= default_provider.find_or_build_record_class(entity_class.name)
35
35
  end
36
36
 
37
- # @return [Hash]
37
+ # @return [Set<Attribute>]
38
+ def attributes
39
+ @attributes ||= Set.new(default_attributes + custom_attributes)
40
+ end
41
+
42
+ # @return [Hash<Set>]
38
43
  # attribute sets classified by the names of their data provider
39
44
  def classified_attributes
40
- @classified_attributes ||= Set.new(custom_attributes + default_attributes).classify(&:provider_name)
45
+ @classified_attributes ||= Set.new(attributes).classify(&:provider_name)
41
46
  end
42
47
 
43
48
  private
@@ -59,7 +59,7 @@ module Datamappify
59
59
  # @yield
60
60
  # an optional block passed to the +Criteria+ {Criteria::Common#initialize initialiser}
61
61
  def build_criteria(name, *args, &block)
62
- Data::Criteria.const_get(class_name).const_get(name).new(*args, &block).result
62
+ Data::Criteria.const_get(class_name).const_get(name).new(*args, &block).perform_with_callbacks
63
63
  end
64
64
  end
65
65
  end
@@ -0,0 +1,12 @@
1
+ module Datamappify
2
+ module Entity
3
+ module LazyChecking
4
+ # Whether the entity is lazy loaded
5
+ #
6
+ # @return [Boolean]
7
+ def lazy_loaded?
8
+ self.respond_to?(:repository)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,12 +1,16 @@
1
+ require 'observer'
1
2
  require 'virtus'
3
+ require 'datamappify/entity/lazy_checking'
2
4
 
3
5
  module Datamappify
4
6
  module Entity
5
7
  def self.included(klass)
6
8
  klass.class_eval do
9
+ include Observable
7
10
  include Virtus
8
11
  include Virtus::Equalizer.new(inspect)
9
12
  include ActiveModel::Validations
13
+ include LazyChecking
10
14
 
11
15
  attribute :id, Integer
12
16
  end
@@ -0,0 +1,123 @@
1
+ module Datamappify
2
+ module Lazy
3
+ # Overrides attribute setters and getters for lazy loading
4
+ class AttributesHandler
5
+ # @return [Array]
6
+ attr_accessor :uncached_attributes
7
+
8
+ # @param entity [Entity]
9
+ def initialize(entity)
10
+ @entity = entity
11
+ @uncached_attributes = entity.attributes.keys
12
+
13
+ entity.add_observer(self)
14
+ end
15
+
16
+ # Triggers only when a reader query (e.g. {Repository::QueryMethod::Find}) is performed
17
+ #
18
+ # @param (see Repository::QueryMethod::Method::SourceAttributesWalker#walk_performed)
19
+ def update(query_method, attributes)
20
+ if query_method && query_method.reader?
21
+ cache_attributes!
22
+ uncached_attributes.each { |name| override_attribute(name) }
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ # @see Data::Mapper#attributes
29
+ #
30
+ # @return (see Data::Mapper#attributes)
31
+ def all_attributes
32
+ @all_attributes ||= @entity.repository.data_mapper.attributes
33
+ end
34
+
35
+ # Removes the cached attributes from the uncached attributes array
36
+ #
37
+ # @return [void]
38
+ def cache_attributes!
39
+ @uncached_attributes = @uncached_attributes - @entity.cached_attributes.keys
40
+ end
41
+
42
+ # Overrides attribute setters and getters
43
+ #
44
+ # @param name [Symbol]
45
+ #
46
+ # @return [void]
47
+ def override_attribute(name)
48
+ override_attribute_setter(name)
49
+ override_attribute_getter(name)
50
+ end
51
+
52
+ # @param (see #override_attribute)
53
+ #
54
+ # @return [void]
55
+ def override_attribute_setter(name)
56
+ @entity.define_singleton_method "#{name}=" do |value|
57
+ super(value)
58
+
59
+ self.define_singleton_method name do
60
+ instance_variable_set "@#{name}", value
61
+ end
62
+ end
63
+ end
64
+
65
+ # @param (see #override_attribute)
66
+ #
67
+ # @return [void]
68
+ def override_attribute_getter(name)
69
+ entity = @entity
70
+ attributes = attributes_from_same_source(name)
71
+
72
+ entity.define_singleton_method name do
73
+ Logger.performed(:override_attribute, name)
74
+
75
+ AttributesHandler.walk_attributes(name, entity, attributes)
76
+
77
+ instance_variable_get "@#{name}"
78
+ end
79
+ end
80
+
81
+ # @param name [Symbol]
82
+ #
83
+ # @return [Set<Data::Mapper::Attribute>]
84
+ def attributes_from_same_source(name)
85
+ source_class_name = attribute_by_name(name).source_class_name
86
+
87
+ attributes = all_attributes.select do |attribute|
88
+ attribute.source_class_name == source_class_name
89
+ end
90
+
91
+ Set.new(attributes)
92
+ end
93
+
94
+ # @param name [Symbol]
95
+ #
96
+ # @return [Data::Mapper::Attribute]
97
+ def attribute_by_name(name)
98
+ all_attributes.find { |attribute| attribute.key == name }
99
+ end
100
+
101
+ class << self
102
+ # @param name [Symbol]
103
+ #
104
+ # @param attributes [Set<Data::Mapper::Attribute>]
105
+ #
106
+ # @return [void]
107
+ def walk_attributes(name, entity, attributes)
108
+ SourceAttributesWalker.new({
109
+ :entity => entity,
110
+ :provider_name => attributes.first.provider_name,
111
+ :attributes => attributes,
112
+ :dirty_aware? => true,
113
+ :dirty_attributes => []
114
+ }).execute do |provider_name, source_class, attributes|
115
+ entity.repository.data_mapper.provider(provider_name).build_criteria(
116
+ :FindByKey, source_class, entity, attributes
117
+ )
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,49 @@
1
+ require 'datamappify/repository/query_method/method/source_attributes_walker'
2
+
3
+ module Datamappify
4
+ module Lazy
5
+ class SourceAttributesWalker < Repository::QueryMethod::Method::SourceAttributesWalker
6
+ private
7
+
8
+ # @param (see Repository::QueryMethod::Method::SourceAttributesWalker#do_walk?)
9
+ #
10
+ # @see Repository::QueryMethod::Method::SourceAttributesWalker#do_walk?
11
+ #
12
+ # @return (see Repository::QueryMethod::Method::SourceAttributesWalker#do_walk?)
13
+ def do_walk?(source_class, attributes)
14
+ read_only? ? default_source_class?(source_class) : true
15
+ end
16
+
17
+ # @param (see Repository::QueryMethod::Method::SourceAttributesWalker#walk_performed)
18
+ #
19
+ # @see Repository::QueryMethod::Method::SourceAttributesWalker#walk_performed
20
+ #
21
+ # @return (see Repository::QueryMethod::Method::SourceAttributesWalker#walk_performed)
22
+ def walk_performed(attributes)
23
+ attributes.each do |attribute|
24
+ @entity.cached_attributes[attribute.key] = attribute.value
25
+ end
26
+
27
+ @entity.changed
28
+ @entity.notify_observers(@query_method, attributes)
29
+
30
+ super
31
+ end
32
+
33
+ # @param source_class [Class]
34
+ #
35
+ # @return [Boolean]
36
+ def default_source_class?(source_class)
37
+ @entity.repository.data_mapper.default_source_class == source_class
38
+ end
39
+
40
+ # Whether the walker is in read-only mode, it is determined from
41
+ # the {Repository::QueryMethod::Method query method} if available
42
+ #
43
+ # @return [Boolean]
44
+ def read_only?
45
+ !!@query_method && @query_method.reader?
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ require 'datamappify/lazy/source_attributes_walker'
2
+ require 'datamappify/lazy/source_attributes_walker'
3
+ require 'datamappify/lazy/attributes_handler'
4
+
5
+ module Datamappify
6
+ module Lazy
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ # @return [Repository]
10
+ cattr_accessor :repository
11
+
12
+ # @return [Hash]
13
+ attr_accessor :cached_attributes
14
+ end
15
+ end
16
+
17
+ def initialize(*args)
18
+ super
19
+
20
+ @cached_attributes = {}
21
+ @attributes_handler = AttributesHandler.new(self)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module Datamappify
2
+ class Logger
3
+ # A meta method to help record whether an action has been performed
4
+ #
5
+ # @param args [any]
6
+ # optional args for logging/tracking purpose
7
+ #
8
+ # @return [TrueClass]
9
+ def self.performed(*args)
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Datamappify
2
+ module Repository
3
+ module LazyChecking
4
+ # Is the repository for an entity that requires lazy loading?
5
+ #
6
+ # @return [Boolean]
7
+ def lazy_load?
8
+ data_mapper.entity_class.included_modules.include?(Datamappify::Lazy)
9
+ end
10
+
11
+ # Assign the repository itself to the lazy entity
12
+ #
13
+ # @return [void]
14
+ def assign_to_entity
15
+ data_mapper.entity_class.repository = self
16
+ end
17
+ end
18
+ end
19
+ end