sequel_mapper 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +43 -0
- data/LICENSE.txt +22 -0
- data/README.md +112 -0
- data/Rakefile +2 -0
- data/TODO.md +33 -0
- data/lib/sequel_mapper.rb +4 -0
- data/lib/sequel_mapper/association_proxy.rb +54 -0
- data/lib/sequel_mapper/belongs_to_association_proxy.rb +27 -0
- data/lib/sequel_mapper/graph.rb +174 -0
- data/lib/sequel_mapper/queryable_association_proxy.rb +23 -0
- data/lib/sequel_mapper/struct_factory.rb +17 -0
- data/lib/sequel_mapper/version.rb +3 -0
- data/sequel_mapper.gemspec +28 -0
- data/spec/graph_persistence_spec.rb +287 -0
- data/spec/graph_traversal_spec.rb +85 -0
- data/spec/ordered_association_spec.rb +29 -0
- data/spec/proxying_spec.rb +82 -0
- data/spec/querying_spec.rb +51 -0
- data/spec/sequel_mapper/association_proxy_spec.rb +95 -0
- data/spec/sequel_mapper/belongs_to_association_proxy_spec.rb +65 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/graph_fixture.rb +331 -0
- data/spec/support/mock_sequel.rb +194 -0
- data/spec/support/query_counter.rb +29 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2a26f63ff570a4dace02b8407237d137e91fcc86
|
4
|
+
data.tar.gz: 682421f11a6baccefbe7969510f345d17ea26d71
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ebc65d4a2915be9f85d269c417f47d96bec45255601a8a389b88c5bdc009420fac2c9f65ad64c8cdb13fea33a877b3d8db0274264e6f3242cd2cff3f207a6111
|
7
|
+
data.tar.gz: 6742b37bb795d34aeadc8fd765bc76976cafcf0e5c1b24873dc5720d1631b07cca1391c3914774acb4b8026514830ea13dcd9bcb7a35d2cb762042fc712898b3
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.env
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.3
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sequel_mapper (0.0.1)
|
5
|
+
sequel (~> 4.16)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
coderay (1.1.0)
|
11
|
+
diff-lcs (1.2.5)
|
12
|
+
method_source (0.8.2)
|
13
|
+
pg (0.17.1)
|
14
|
+
pry (0.10.1)
|
15
|
+
coderay (~> 1.1.0)
|
16
|
+
method_source (~> 0.8.1)
|
17
|
+
slop (~> 3.4)
|
18
|
+
rake (10.1.0)
|
19
|
+
rspec (3.1.0)
|
20
|
+
rspec-core (~> 3.1.0)
|
21
|
+
rspec-expectations (~> 3.1.0)
|
22
|
+
rspec-mocks (~> 3.1.0)
|
23
|
+
rspec-core (3.1.7)
|
24
|
+
rspec-support (~> 3.1.0)
|
25
|
+
rspec-expectations (3.1.2)
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
+
rspec-support (~> 3.1.0)
|
28
|
+
rspec-mocks (3.1.3)
|
29
|
+
rspec-support (~> 3.1.0)
|
30
|
+
rspec-support (3.1.2)
|
31
|
+
sequel (4.16.0)
|
32
|
+
slop (3.6.0)
|
33
|
+
|
34
|
+
PLATFORMS
|
35
|
+
ruby
|
36
|
+
|
37
|
+
DEPENDENCIES
|
38
|
+
bundler (~> 1.7)
|
39
|
+
pg (~> 0.17.1)
|
40
|
+
pry (~> 0.10.1)
|
41
|
+
rake (~> 10.0)
|
42
|
+
rspec (~> 3.1)
|
43
|
+
sequel_mapper!
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Stephen Best
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# SequelMapper
|
2
|
+
|
3
|
+
**Very new, much experimental, so incomplete**
|
4
|
+
|
5
|
+
## What it is
|
6
|
+
|
7
|
+
SequelMapper is a data mapper that pulls rows out of your database and maps
|
8
|
+
them into a graph of plain Ruby objects. The graph can then be modifed and
|
9
|
+
persisted back into the database as a whole.
|
10
|
+
|
11
|
+
The main feature is that it fully supports all the kinds of data associations
|
12
|
+
that you are used to with ActiveRecord but for your POROs.
|
13
|
+
|
14
|
+
It is built on top of Jeremy Evans' Sequel library.
|
15
|
+
|
16
|
+
## Why is it?
|
17
|
+
|
18
|
+
* It seems like ROM may not be finished any time soon and I felt I could put
|
19
|
+
together something functional albeit less ambitious
|
20
|
+
* I love the Sequel library
|
21
|
+
* I love decoupling persistence
|
22
|
+
* Writing a complex datamapper is sure way to stall your project so I'm writing
|
23
|
+
one for you
|
24
|
+
* I am a sick person who enjoys this sort of thing
|
25
|
+
|
26
|
+
So go on, persist those POROs, they don't even have to know about it.
|
27
|
+
|
28
|
+
## Example
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
# Let's say you have some domain objects
|
32
|
+
|
33
|
+
User = Struct.new(:id, :first_name, :last_name, :email, :posts)
|
34
|
+
Post = Struct.new(:id, :author, :subject, :body, :comments, :categories)
|
35
|
+
Comment = Struct.new(:id, :post, :commenter, :body)
|
36
|
+
Category = Struct.new(:id, :name, :posts)
|
37
|
+
|
38
|
+
# And a relational database with some tables that look similar
|
39
|
+
|
40
|
+
DB = Sequel.postgres(
|
41
|
+
host: ENV.fetch("PGHOST"),
|
42
|
+
user: ENV.fetch("PGUSER"),
|
43
|
+
database: ENV.fetch("PGDATABASE"),
|
44
|
+
)
|
45
|
+
|
46
|
+
user_mapper = SequelMapper::Graph.new(
|
47
|
+
top_level_namespace: :users,
|
48
|
+
datastore: DB,
|
49
|
+
config: mapper_config, # Config omitted
|
50
|
+
)
|
51
|
+
|
52
|
+
# Then this may appeal to you
|
53
|
+
|
54
|
+
user = user_mapper.where(id: 1).first
|
55
|
+
# => [#<struct User
|
56
|
+
# id=1,
|
57
|
+
# first_name="Stephen",
|
58
|
+
# last_name="Best",
|
59
|
+
# email="bestie@gmail.com",
|
60
|
+
# posts=#<SequelMapper::AssociationProxy:0x007ffbc3c7cb50 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>>]
|
61
|
+
|
62
|
+
user.posts
|
63
|
+
# => #<SequelMapper::AssociationProxy:0x007ffbc3c7cb50 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>
|
64
|
+
# That's lazily evaluated try ...
|
65
|
+
|
66
|
+
user.posts.to_a
|
67
|
+
# => [#<struct Post
|
68
|
+
# id=1,
|
69
|
+
# author=
|
70
|
+
# #<struct User
|
71
|
+
# id=1,
|
72
|
+
# first_name="Stephen",
|
73
|
+
# last_name="Best",
|
74
|
+
# email="bestie@gmail.com",
|
75
|
+
# posts=#<SequelMapper::AssociationProxy:0x007ffbc3c7cb50 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>>,
|
76
|
+
# subject="Object mapping",
|
77
|
+
# body="It is often tricky",
|
78
|
+
# comments=#<SequelMapper::AssociationProxy:0x007ffbc59377b8 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>,
|
79
|
+
# categories=#<SequelMapper::AssociationProxy:0x007ffbc5936138 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>>,
|
80
|
+
# #<struct Post
|
81
|
+
# id=2,
|
82
|
+
# author=
|
83
|
+
# #<struct User
|
84
|
+
# id=1,
|
85
|
+
# first_name="Stephen",
|
86
|
+
# last_name="Best",
|
87
|
+
# email="bestie@gmail.com",
|
88
|
+
# posts=#<SequelMapper::AssociationProxy:0x007ffbc3c7cb50 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>>,
|
89
|
+
# subject="Object mapping part 2",
|
90
|
+
# body="Lazy load all the things!",
|
91
|
+
# comments=#<SequelMapper::AssociationProxy:0x007ffbc5935990 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>,
|
92
|
+
# categories=#<SequelMapper::AssociationProxy:0x007ffbc592fe50 @assoc_enum=#<Enumerator::Lazy: ...>, @removed_nodes=[]>>]
|
93
|
+
|
94
|
+
# And then access the comments and so on ...
|
95
|
+
```
|
96
|
+
|
97
|
+
## Installation
|
98
|
+
|
99
|
+
Add this line to your application's Gemfile:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
gem 'sequel_mapper'
|
103
|
+
```
|
104
|
+
|
105
|
+
And then execute:
|
106
|
+
|
107
|
+
$ bundle
|
108
|
+
|
109
|
+
Or install it yourself as:
|
110
|
+
|
111
|
+
$ gem install sequel_mapper
|
112
|
+
|
data/Rakefile
ADDED
data/TODO.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# TODOs
|
2
|
+
|
3
|
+
In no particular order
|
4
|
+
|
5
|
+
## General
|
6
|
+
* Refactor, methods too, big objects missing
|
7
|
+
|
8
|
+
## Persistence
|
9
|
+
* Efficient saving
|
10
|
+
- Part one, if it wasn't loaded it wasn't modified, check identity map
|
11
|
+
- Part two, dirty tracking
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
* Automatic config generation based on schema, foreign keys etc
|
15
|
+
* Config to take either a classes or callable factory
|
16
|
+
|
17
|
+
## Querying
|
18
|
+
* Querying API, what would a repository with some arbitrary queries look like?
|
19
|
+
- e.g. an association on post called `burger_comments` that finds comments
|
20
|
+
with the word burger in them
|
21
|
+
* Add other querying methods from assocaition proxies or remove entirely
|
22
|
+
- Depends on nailing down the querying API
|
23
|
+
* When possible optimise blocks given to `AssociationProxy#select` with
|
24
|
+
Sequel's `#where` with block [querying API](http://sequel.jeremyevans.net/rdoc/files/doc/cheat_sheet_rdoc.html#label-AND%2FOR%2FNOT)
|
25
|
+
|
26
|
+
## Associations
|
27
|
+
* Eager loading
|
28
|
+
* Read only associations
|
29
|
+
- Loaded objects would be immutable
|
30
|
+
- Collection proxy would have no #push or #remove
|
31
|
+
- Skipped when dumping
|
32
|
+
* Associations defined with a join
|
33
|
+
* Composable associations
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module SequelMapper
|
4
|
+
class AssociationProxy
|
5
|
+
def initialize(assoc_enum)
|
6
|
+
@assoc_enum = assoc_enum
|
7
|
+
@added_nodes = []
|
8
|
+
@removed_nodes = []
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :assoc_enum, :removed_nodes, :added_nodes
|
12
|
+
private :assoc_enum
|
13
|
+
|
14
|
+
include Enumerable
|
15
|
+
def each(&block)
|
16
|
+
enum = Enumerator.new do |yielder|
|
17
|
+
assoc_enum.each do |element|
|
18
|
+
yielder.yield(element) unless removed?(element)
|
19
|
+
end
|
20
|
+
|
21
|
+
@added_nodes.each do |node|
|
22
|
+
yielder.yield(node)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if block
|
27
|
+
enum.each(&block)
|
28
|
+
self
|
29
|
+
else
|
30
|
+
enum
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove(node)
|
35
|
+
@removed_nodes.push(node)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def push(node)
|
40
|
+
@added_nodes.push(node)
|
41
|
+
end
|
42
|
+
|
43
|
+
def where(criteria)
|
44
|
+
@assoc_enum.where(criteria)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def removed?(node)
|
51
|
+
@removed_nodes.include?(node)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "delegate"
|
2
|
+
|
3
|
+
class BelongsToAssociationProxy < SimpleDelegator
|
4
|
+
def initialize(object_loader)
|
5
|
+
@object_loader = object_loader
|
6
|
+
@loaded = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method_id, *args, &block)
|
10
|
+
__load_object__
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def __getobj__
|
16
|
+
__load_object__
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def __load_object__
|
23
|
+
__setobj__(@object_loader.call).tap {
|
24
|
+
@loaded = true
|
25
|
+
} unless @loaded
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require "sequel_mapper/association_proxy"
|
2
|
+
require "sequel_mapper/belongs_to_association_proxy"
|
3
|
+
require "sequel_mapper/queryable_association_proxy"
|
4
|
+
|
5
|
+
module SequelMapper
|
6
|
+
class Graph
|
7
|
+
def initialize(datastore:, top_level_namespace:, relation_mappings:)
|
8
|
+
@top_level_namespace = top_level_namespace
|
9
|
+
@datastore = datastore
|
10
|
+
@relation_mappings = relation_mappings
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :top_level_namespace, :datastore, :relation_mappings
|
14
|
+
private :top_level_namespace, :datastore, :relation_mappings
|
15
|
+
|
16
|
+
def where(criteria)
|
17
|
+
datastore[top_level_namespace]
|
18
|
+
.where(criteria)
|
19
|
+
.map { |row|
|
20
|
+
load(
|
21
|
+
relation_mappings.fetch(top_level_namespace),
|
22
|
+
row,
|
23
|
+
)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def save(graph_root)
|
28
|
+
@persisted_objects = []
|
29
|
+
dump(top_level_namespace, graph_root)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def identity_map
|
35
|
+
@identity_map ||= {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def dump(relation_name, object)
|
39
|
+
return if @persisted_objects.include?(object)
|
40
|
+
@persisted_objects.push(object)
|
41
|
+
|
42
|
+
relation = relation_mappings.fetch(relation_name)
|
43
|
+
|
44
|
+
row = object.to_h.select { |field_name, _v|
|
45
|
+
relation.fetch(:columns).include?(field_name)
|
46
|
+
}
|
47
|
+
|
48
|
+
relation.fetch(:belongs_to, []).each do |assoc_name, assoc_config|
|
49
|
+
row[assoc_config.fetch(:foreign_key)] = object.public_send(assoc_name).id
|
50
|
+
end
|
51
|
+
|
52
|
+
relation.fetch(:has_many, []).each do |assoc_name, assoc_config|
|
53
|
+
object.public_send(assoc_name).each do |assoc_object|
|
54
|
+
dump(assoc_config.fetch(:relation_name), assoc_object)
|
55
|
+
end
|
56
|
+
|
57
|
+
next unless object.public_send(assoc_name).respond_to?(:removed_nodes)
|
58
|
+
object.public_send(assoc_name).removed_nodes.each do |removed_node|
|
59
|
+
datastore[assoc_config.fetch(:relation_name)]
|
60
|
+
.where(id: removed_node.id)
|
61
|
+
.delete
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
relation.fetch(:has_many_through, []).each do |assoc_name, assoc_config|
|
66
|
+
object.public_send(assoc_name).each do |assoc_object|
|
67
|
+
dump(assoc_config.fetch(:relation_name), assoc_object)
|
68
|
+
end
|
69
|
+
|
70
|
+
next unless object.public_send(assoc_name).respond_to?(:added_nodes)
|
71
|
+
object.public_send(assoc_name).added_nodes.each do |added_node|
|
72
|
+
datastore[assoc_config.fetch(:through_relation_name)]
|
73
|
+
.insert(
|
74
|
+
assoc_config.fetch(:foreign_key) => object.id,
|
75
|
+
assoc_config.fetch(:association_foreign_key) => added_node.id,
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
object.public_send(assoc_name).removed_nodes.each do |removed_node|
|
80
|
+
datastore[assoc_config.fetch(:through_relation_name)]
|
81
|
+
.where(assoc_config.fetch(:association_foreign_key) => removed_node.id)
|
82
|
+
.delete
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
existing = datastore[relation_name]
|
87
|
+
.where(id: object.id)
|
88
|
+
|
89
|
+
if existing.empty?
|
90
|
+
datastore[relation_name].insert(row)
|
91
|
+
else
|
92
|
+
existing.update(row)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def load(relation, row)
|
97
|
+
previously_loaded_object = identity_map.fetch(row.fetch(:id), false)
|
98
|
+
return previously_loaded_object if previously_loaded_object
|
99
|
+
|
100
|
+
# puts "****************LOADING #{row.fetch(:id)}"
|
101
|
+
|
102
|
+
has_many_associations = Hash[
|
103
|
+
relation.fetch(:has_many, []).map { |assoc_name, assoc|
|
104
|
+
data_enum = datastore[assoc.fetch(:relation_name)]
|
105
|
+
.where(assoc.fetch(:foreign_key) => row.fetch(:id))
|
106
|
+
|
107
|
+
if assoc.fetch(:order_by, false)
|
108
|
+
data_enum = data_enum.order(assoc.fetch(:order_by, {}).fetch(:columns, []))
|
109
|
+
|
110
|
+
if assoc.fetch(:order_by).fetch(:direction, :asc) == :desc
|
111
|
+
data_enum = data_enum.reverse
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
[
|
116
|
+
assoc_name,
|
117
|
+
AssociationProxy.new(
|
118
|
+
QueryableAssociationProxy.new(
|
119
|
+
data_enum,
|
120
|
+
->(row) {
|
121
|
+
load(relation_mappings.fetch(assoc.fetch(:relation_name)), row)
|
122
|
+
},
|
123
|
+
)
|
124
|
+
)
|
125
|
+
]
|
126
|
+
}
|
127
|
+
]
|
128
|
+
|
129
|
+
belongs_to_associations = Hash[
|
130
|
+
relation.fetch(:belongs_to, []).map { |assoc_name, assoc|
|
131
|
+
[
|
132
|
+
assoc_name,
|
133
|
+
BelongsToAssociationProxy.new(
|
134
|
+
datastore[assoc.fetch(:relation_name)]
|
135
|
+
.where(:id => row.fetch(assoc.fetch(:foreign_key)))
|
136
|
+
.lazy
|
137
|
+
.map { |row|
|
138
|
+
load(relation_mappings.fetch(assoc.fetch(:relation_name)), row)
|
139
|
+
}
|
140
|
+
.public_method(:first)
|
141
|
+
)
|
142
|
+
]
|
143
|
+
}
|
144
|
+
]
|
145
|
+
|
146
|
+
has_many_through_assocations = Hash[
|
147
|
+
relation.fetch(:has_many_through, []).map { |assoc_name, assoc|
|
148
|
+
[
|
149
|
+
assoc_name,
|
150
|
+
AssociationProxy.new(
|
151
|
+
QueryableAssociationProxy.new(
|
152
|
+
datastore[assoc.fetch(:relation_name)]
|
153
|
+
.join(assoc.fetch(:through_relation_name), assoc.fetch(:association_foreign_key) => :id)
|
154
|
+
.where(assoc.fetch(:foreign_key) => row.fetch(:id)),
|
155
|
+
->(row) {
|
156
|
+
load(relation_mappings.fetch(assoc.fetch(:relation_name)), row)
|
157
|
+
},
|
158
|
+
)
|
159
|
+
)
|
160
|
+
]
|
161
|
+
}
|
162
|
+
]
|
163
|
+
|
164
|
+
relation.fetch(:factory).call(
|
165
|
+
row
|
166
|
+
.merge(has_many_associations)
|
167
|
+
.merge(has_many_through_assocations)
|
168
|
+
.merge(belongs_to_associations)
|
169
|
+
).tap { |object|
|
170
|
+
identity_map.store(row.fetch(:id), object)
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|