vorpal 1.0.2 → 1.3.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.
- checksums.yaml +4 -4
- data/README.md +73 -26
- data/lib/vorpal/aggregate_mapper.rb +13 -2
- data/lib/vorpal/aggregate_traversal.rb +9 -8
- data/lib/vorpal/config/association_config.rb +84 -0
- data/lib/vorpal/config/belongs_to_config.rb +35 -0
- data/lib/vorpal/config/class_config.rb +71 -0
- data/lib/vorpal/config/configs.rb +54 -0
- data/lib/vorpal/config/foreign_key_info.rb +23 -0
- data/lib/vorpal/config/has_many_config.rb +38 -0
- data/lib/vorpal/config/has_one_config.rb +35 -0
- data/lib/vorpal/config/main_config.rb +68 -0
- data/lib/vorpal/db_loader.rb +25 -22
- data/lib/vorpal/driver/postgresql.rb +42 -6
- data/lib/vorpal/dsl/config_builder.rb +26 -73
- data/lib/vorpal/dsl/configuration.rb +139 -42
- data/lib/vorpal/dsl/defaults_generator.rb +1 -1
- data/lib/vorpal/engine.rb +27 -13
- data/lib/vorpal/exceptions.rb +4 -0
- data/lib/vorpal/identity_map.rb +7 -2
- data/lib/vorpal/loaded_objects.rb +57 -14
- data/lib/vorpal/util/array_hash.rb +22 -8
- data/lib/vorpal/util/hash_initialization.rb +1 -1
- data/lib/vorpal/version.rb +1 -1
- data/vorpal.gemspec +4 -5
- metadata +18 -78
- data/.editorconfig +0 -13
- data/.envrc +0 -4
- data/.gitignore +0 -16
- data/.rspec +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -18
- data/.yardopts +0 -1
- data/Appraisals +0 -18
- data/Gemfile +0 -4
- data/Rakefile +0 -39
- data/bin/appraisal +0 -29
- data/bin/rake +0 -29
- data/bin/rspec +0 -29
- data/docker-compose.yml +0 -19
- data/gemfiles/rails_5_1.gemfile +0 -11
- data/gemfiles/rails_5_1.gemfile.lock +0 -101
- data/gemfiles/rails_5_2.gemfile +0 -11
- data/gemfiles/rails_5_2.gemfile.lock +0 -101
- data/gemfiles/rails_6_0.gemfile +0 -9
- data/gemfiles/rails_6_0.gemfile.lock +0 -101
- data/lib/vorpal/configs.rb +0 -296
- data/spec/acceptance/vorpal/aggregate_mapper_spec.rb +0 -910
- data/spec/helpers/codecov_helper.rb +0 -7
- data/spec/helpers/db_helpers.rb +0 -69
- data/spec/helpers/profile_helpers.rb +0 -26
- data/spec/integration/vorpal/driver/postgresql_spec.rb +0 -42
- data/spec/integration_spec_helper.rb +0 -29
- data/spec/performance/vorpal/performance_spec.rb +0 -305
- data/spec/unit/vorpal/configs_spec.rb +0 -117
- data/spec/unit/vorpal/db_loader_spec.rb +0 -103
- data/spec/unit/vorpal/dsl/config_builder_spec.rb +0 -18
- data/spec/unit/vorpal/dsl/defaults_generator_spec.rb +0 -75
- data/spec/unit/vorpal/identity_map_spec.rb +0 -62
- data/spec/unit/vorpal/loaded_objects_spec.rb +0 -22
- data/spec/unit/vorpal/util/string_utils_spec.rb +0 -25
- data/spec/unit_spec_helper.rb +0 -1
@@ -1,57 +1,154 @@
|
|
1
1
|
require 'vorpal/engine'
|
2
2
|
require 'vorpal/dsl/config_builder'
|
3
3
|
require 'vorpal/driver/postgresql'
|
4
|
+
require 'vorpal/config/main_config'
|
4
5
|
|
5
6
|
module Vorpal
|
6
7
|
module Dsl
|
7
|
-
|
8
|
-
|
9
|
-
# Configures and creates a {Engine} instance.
|
10
|
-
#
|
11
|
-
# @param options [Hash] Global configuration options for the engine instance.
|
12
|
-
# @option options [Object] :db_driver (Object that will be used to interact with the DB.)
|
13
|
-
# Must be duck-type compatible with {Postgresql}.
|
8
|
+
# Implements the Vorpal DSL.
|
14
9
|
#
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# Maps a domain class to a relational table.
|
10
|
+
# ```ruby
|
11
|
+
# engine = Vorpal.define do
|
12
|
+
# map Tree do
|
13
|
+
# attributes :name
|
14
|
+
# belongs_to :trunk
|
15
|
+
# has_many :branches
|
16
|
+
# end
|
23
17
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# Optional, if one is not specified, it will be generated.
|
29
|
-
# @option options [Object] :serializer (map the {ConfigBuilder#attributes} directly)
|
30
|
-
# Object that will convert the domain objects into a hash.
|
18
|
+
# map Trunk do
|
19
|
+
# attributes :length
|
20
|
+
# has_one :tree
|
21
|
+
# end
|
31
22
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
23
|
+
# map Branch do
|
24
|
+
# attributes :length
|
25
|
+
# belongs_to :tree
|
26
|
+
# end
|
27
|
+
# end
|
36
28
|
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
29
|
+
# mapper = engine.mapper_for(Tree)
|
30
|
+
# ```
|
31
|
+
module Configuration
|
32
|
+
# Configures and creates a {Engine} instance.
|
33
|
+
#
|
34
|
+
# @param options [Hash] Global configuration options for the engine instance.
|
35
|
+
# @option options [Object] :db_driver (Object that will be used to interact with the DB.)
|
36
|
+
# Must be duck-type compatible with {Postgresql}.
|
37
|
+
#
|
38
|
+
# @return [Engine] Instance of the mapping engine.
|
39
|
+
def define(options={}, &block)
|
40
|
+
@main_config = Config::MainConfig.new
|
41
|
+
instance_exec(&block)
|
42
|
+
@main_config.initialize_association_configs
|
43
|
+
db_driver = options.fetch(:db_driver, Driver::Postgresql.new)
|
44
|
+
engine = Engine.new(db_driver, @main_config)
|
45
|
+
@main_config = nil # make sure this MainConfig is never re-used by accident.
|
46
|
+
engine
|
47
|
+
end
|
41
48
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
# Maps a domain class to a relational table.
|
50
|
+
#
|
51
|
+
# @param domain_class [Class] Type of the domain model to be mapped
|
52
|
+
# @param options [Hash] Configure how to map the domain model
|
53
|
+
# @option options [String] :to
|
54
|
+
# Class of the ActiveRecord object that will map this domain class to the DB.
|
55
|
+
# Optional, if one is not specified, it will be generated.
|
56
|
+
# @option options [Object] :serializer (map the {ConfigBuilder#attributes} directly)
|
57
|
+
# Object that will convert the domain objects into a hash.
|
58
|
+
#
|
59
|
+
# Must have a `(Hash) serialize(Object)` method.
|
60
|
+
# @option options [Object] :deserializer (map the {ConfigBuilder#attributes} directly)
|
61
|
+
# Object that will set a hash of attribute_names->values onto a new domain
|
62
|
+
# object.
|
63
|
+
#
|
64
|
+
# Must have a `(Object) deserialize(Object, Hash)` method.
|
65
|
+
# @option options [Symbol] :primary_key_type [:serial, :uuid] (:serial)
|
66
|
+
# The type of primary key for the class. :serial for auto-incrementing integer, :uuid for a UUID
|
67
|
+
# @option options [Symbol] :id
|
68
|
+
# Same as :primary_key_type. Exists for compatibility with the Rails API.
|
69
|
+
def map(domain_class, options={}, &block)
|
70
|
+
class_config = build_class_config(domain_class, options, &block)
|
71
|
+
@main_config.add_class_config(class_config)
|
72
|
+
class_config
|
73
|
+
end
|
48
74
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
75
|
+
# @private
|
76
|
+
def build_class_config(domain_class, options, &block)
|
77
|
+
@builder = ConfigBuilder.new(domain_class, options, Driver::Postgresql.new)
|
78
|
+
instance_exec(&block) if block_given?
|
79
|
+
class_config = @builder.build
|
80
|
+
@builder = nil # make sure this ConfigBuilder is never re-used by accident.
|
81
|
+
class_config
|
82
|
+
end
|
83
|
+
|
84
|
+
# Maps the given attributes to and from the domain object and the DB. Not needed
|
85
|
+
# if a serializer and deserializer were provided.
|
86
|
+
def attributes(*attributes)
|
87
|
+
@builder.attributes(*attributes)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Defines a one-to-many association to another type where the foreign key is stored on the associated table.
|
91
|
+
#
|
92
|
+
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
93
|
+
# traversed in one direction: from the type that defines the association (the one with the
|
94
|
+
# getter) to the type that is associated.
|
95
|
+
#
|
96
|
+
# @param name [String] Name of the association getter.
|
97
|
+
# @param options [Hash]
|
98
|
+
# @option options [Boolean] :owned (True) True if the associated type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
99
|
+
# @option options [String] :fk (Association-owning class name converted to snakecase and appended with a '_id') The name of the DB column on the associated table that contains the foreign key reference to the association owner.
|
100
|
+
# @option options [String] :fk_type The name of the DB column on the associated table that contains the association-owning class name. Only needed when the associated end is polymorphic.
|
101
|
+
# @option options [String] :unique_key_name ("id") The name of the column on the owning table that the foreign key points to. Normally the primary key column.
|
102
|
+
# @option options [String] :primary_key Same as :unique_key_name. Exists for compatibility with Rails API.
|
103
|
+
# @option options [Class] :child_class DEPRECATED. Use `associated_class` instead. The associated class.
|
104
|
+
# @option options [Class] :associated_class (Name of the association converted to a Class) The associated class.
|
105
|
+
def has_many(name, options={})
|
106
|
+
@builder.has_many(name, options)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Defines a one-to-one association to another type where the foreign key
|
110
|
+
# is stored on the associated table.
|
111
|
+
#
|
112
|
+
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
113
|
+
# traversed in one direction: from the type that defines the association (the one with the
|
114
|
+
# getter) to the type that is associated.
|
115
|
+
#
|
116
|
+
# @param name [String] Name of the association getter.
|
117
|
+
# @param options [Hash]
|
118
|
+
# @option options [Boolean] :owned (True) True if the associated type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
119
|
+
# @option options [String] :fk (Association-owning class name converted to snakecase and appended with a '_id') The name of the DB column on the associated table that contains the foreign key reference to the association owner.
|
120
|
+
# @option options [String] :fk_type The name of the DB column on the associated table that contains the association-owning class name. Only needed when the associated end is polymorphic.
|
121
|
+
# @option options [String] :unique_key_name ("id") The name of the column on the owning table that the foreign key points to. Normally the primary key column.
|
122
|
+
# @option options [String] :primary_key Same as :unique_key_name. Exists for compatibility with Rails API.
|
123
|
+
# @option options [Class] :child_class DEPRECATED. Use `associated_class` instead. The associated class.
|
124
|
+
# @option options [Class] :associated_class (Name of the association converted to a Class) The associated class.
|
125
|
+
def has_one(name, options={})
|
126
|
+
@builder.has_one(name, options)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Defines a one-to-one association with another type where the foreign key
|
130
|
+
# is stored on the table of the entity declaring the association.
|
131
|
+
#
|
132
|
+
# This association can be polymorphic. I.E. associates can be of different types.
|
133
|
+
#
|
134
|
+
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
135
|
+
# traversed in one direction: from the type that defines the association (the one with the
|
136
|
+
# getter) to the type that is associated.
|
137
|
+
#
|
138
|
+
# @param name [String] Name of the association getter.
|
139
|
+
# @param options [Hash]
|
140
|
+
# @option options [Boolean] :owned (True) True if the associated type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
141
|
+
# @option options [String] :fk (Associated class name converted to snakecase and appended with a '_id') The name of the DB column on the association-owning table that contains the foreign key reference to the associated table.
|
142
|
+
# @option options [String] :fk_type The name of the DB column on the association-owning table that contains the associated class name. Only needed when the association is polymorphic.
|
143
|
+
# @option options [String] :unique_key_name ("id") The name of the column on the associated table that the foreign key points to. Normally the primary key column.
|
144
|
+
# @option options [String] :primary_key Same as :unique_key_name. Exists for compatibility with Rails API.
|
145
|
+
# @option options [Class] :child_class DEPRECATED. Use `associated_class` instead. The associated class.
|
146
|
+
# @option options [Class] :associated_class (Name of the association converted to a Class) The associated class.
|
147
|
+
# @option options [[Class]] :child_classes DEPRECATED. Use `associated_classes` instead. The list of possible classes that can be associated. This is for polymorphic associations. Takes precedence over `:associated_class`.
|
148
|
+
# @option options [[Class]] :associated_classes (Name of the association converted to a Class) The list of possible classes that can be associated. This is for polymorphic associations. Takes precedence over `:associated_class`.
|
149
|
+
def belongs_to(name, options={})
|
150
|
+
@builder.belongs_to(name, options)
|
151
|
+
end
|
54
152
|
end
|
55
153
|
end
|
56
|
-
end
|
57
154
|
end
|
data/lib/vorpal/engine.rb
CHANGED
@@ -7,9 +7,9 @@ require 'vorpal/exceptions'
|
|
7
7
|
module Vorpal
|
8
8
|
class Engine
|
9
9
|
# @private
|
10
|
-
def initialize(db_driver,
|
10
|
+
def initialize(db_driver, main_config)
|
11
11
|
@db_driver = db_driver
|
12
|
-
@configs =
|
12
|
+
@configs = main_config
|
13
13
|
end
|
14
14
|
|
15
15
|
# Creates a mapper for saving/updating/loading/destroying an aggregate to/from
|
@@ -34,6 +34,9 @@ module Vorpal
|
|
34
34
|
serialize(all_owned_objects, mapping, loaded_db_objects)
|
35
35
|
new_objects = get_unsaved_objects(mapping.keys)
|
36
36
|
begin
|
37
|
+
# Primary keys are set eagerly (instead of waiting for them to be set by ActiveRecord upon create)
|
38
|
+
# because we want to support non-null FK constraints without needing to figure the correct
|
39
|
+
# order to save entities in.
|
37
40
|
set_primary_keys(all_owned_objects, mapping)
|
38
41
|
set_foreign_keys(all_owned_objects, mapping)
|
39
42
|
remove_orphans(mapping, loaded_db_objects)
|
@@ -89,10 +92,16 @@ module Vorpal
|
|
89
92
|
@configs.config_for(domain_class).db_class
|
90
93
|
end
|
91
94
|
|
95
|
+
# Try to use {AggregateMapper#query} instead.
|
92
96
|
def query(domain_class)
|
93
97
|
@db_driver.query(@configs.config_for(domain_class).db_class, mapper_for(domain_class))
|
94
98
|
end
|
95
99
|
|
100
|
+
# @private
|
101
|
+
def class_config(domain_class)
|
102
|
+
@configs.config_for(domain_class)
|
103
|
+
end
|
104
|
+
|
96
105
|
private
|
97
106
|
|
98
107
|
def wrap(collection_or_not)
|
@@ -128,8 +137,9 @@ module Vorpal
|
|
128
137
|
loaded_db_objects.each do |config, db_objects|
|
129
138
|
db_objects.each do |db_object|
|
130
139
|
config.local_association_configs.each do |association_config|
|
131
|
-
db_remote = loaded_db_objects.
|
140
|
+
db_remote = loaded_db_objects.find_by_unique_key(
|
132
141
|
association_config.remote_class_config(db_object),
|
142
|
+
association_config.unique_key_name,
|
133
143
|
association_config.fk_value(db_object)
|
134
144
|
)
|
135
145
|
association_config.associate(identity_map.get(db_object), identity_map.get(db_remote))
|
@@ -150,10 +160,10 @@ module Vorpal
|
|
150
160
|
def serialize_object(object, config, loaded_db_objects)
|
151
161
|
if config.serialization_required?
|
152
162
|
attributes = config.serialize(object)
|
153
|
-
|
163
|
+
db_object = loaded_db_objects.find_by_primary_key(config, object)
|
164
|
+
if object.id.nil? || db_object.nil? # object doesn't exist in the DB
|
154
165
|
config.build_db_object(attributes)
|
155
166
|
else
|
156
|
-
db_object = loaded_db_objects.find_by_id(config, object.id)
|
157
167
|
config.set_db_object_attributes(db_object, attributes)
|
158
168
|
db_object
|
159
169
|
end
|
@@ -165,7 +175,11 @@ module Vorpal
|
|
165
175
|
def set_primary_keys(owned_objects, mapping)
|
166
176
|
owned_objects.each do |config, objects|
|
167
177
|
in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
|
168
|
-
|
178
|
+
if config.primary_key_type == :uuid
|
179
|
+
primary_keys = Array.new(in_need_of_primary_keys.length) { SecureRandom.uuid }
|
180
|
+
elsif config.primary_key_type == :serial
|
181
|
+
primary_keys = @db_driver.get_primary_keys(config.db_class, in_need_of_primary_keys.length)
|
182
|
+
end
|
169
183
|
in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
|
170
184
|
mapping[object].id = primary_key
|
171
185
|
object.id = primary_key
|
@@ -179,23 +193,23 @@ module Vorpal
|
|
179
193
|
objects.each do |object|
|
180
194
|
config.has_manys.each do |has_many_config|
|
181
195
|
if has_many_config.owned
|
182
|
-
|
183
|
-
|
184
|
-
has_many_config.set_foreign_key(mapping[
|
196
|
+
associates = has_many_config.get_associated(object)
|
197
|
+
associates.each do |associate|
|
198
|
+
has_many_config.set_foreign_key(mapping[associate], object)
|
185
199
|
end
|
186
200
|
end
|
187
201
|
end
|
188
202
|
|
189
203
|
config.has_ones.each do |has_one_config|
|
190
204
|
if has_one_config.owned
|
191
|
-
|
192
|
-
has_one_config.set_foreign_key(mapping[
|
205
|
+
associate = has_one_config.get_associated(object)
|
206
|
+
has_one_config.set_foreign_key(mapping[associate], object) if associate
|
193
207
|
end
|
194
208
|
end
|
195
209
|
|
196
210
|
config.belongs_tos.each do |belongs_to_config|
|
197
|
-
|
198
|
-
belongs_to_config.set_foreign_key(mapping[object],
|
211
|
+
associate = belongs_to_config.get_associated(object)
|
212
|
+
belongs_to_config.set_foreign_key(mapping[object], associate)
|
199
213
|
end
|
200
214
|
end
|
201
215
|
end
|
data/lib/vorpal/exceptions.rb
CHANGED
data/lib/vorpal/identity_map.rb
CHANGED
@@ -28,9 +28,14 @@ module Vorpal
|
|
28
28
|
|
29
29
|
def key(db_row)
|
30
30
|
return nil unless db_row
|
31
|
-
|
31
|
+
primary_key_value = get_primary_key_value(db_row)
|
32
|
+
raise "Cannot map a DB row without an id '#{db_row.inspect}' to an entity." if primary_key_value.nil?
|
32
33
|
raise "Cannot map a DB row without a Class with a name '#{db_row.inspect}' to an entity." if db_row.class.name.nil?
|
33
|
-
[
|
34
|
+
[primary_key_value, db_row.class.name]
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_primary_key_value(db_row)
|
38
|
+
db_row.send(db_row.class.primary_key)
|
34
39
|
end
|
35
40
|
end
|
36
41
|
end
|
@@ -5,37 +5,80 @@ module Vorpal
|
|
5
5
|
|
6
6
|
# @private
|
7
7
|
class LoadedObjects
|
8
|
-
include Util::ArrayHash
|
9
8
|
extend Forwardable
|
10
9
|
include Enumerable
|
11
10
|
|
12
|
-
|
13
|
-
def_delegators :objects, :each
|
11
|
+
def_delegators :@objects, :each
|
14
12
|
|
15
13
|
def initialize
|
16
|
-
@objects =
|
17
|
-
@
|
14
|
+
@objects = Util::ArrayHash.new
|
15
|
+
@cache = {}
|
18
16
|
end
|
19
17
|
|
20
18
|
def add(config, objects)
|
21
19
|
objects_to_add = objects.map do |object|
|
22
|
-
if !already_loaded?(config, object
|
23
|
-
|
20
|
+
if !already_loaded?(config, object)
|
21
|
+
add_to_cache(config, object)
|
24
22
|
end
|
25
|
-
end
|
26
|
-
|
23
|
+
end.compact
|
24
|
+
@objects.append(config, objects_to_add)
|
25
|
+
objects_to_add
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_primary_key(config, object)
|
29
|
+
find_by_unique_key(config, "id", object.id)
|
27
30
|
end
|
28
31
|
|
29
|
-
def
|
30
|
-
|
32
|
+
def find_by_unique_key(config, column_name, value)
|
33
|
+
get_from_cache(config, column_name, value)
|
31
34
|
end
|
32
35
|
|
33
36
|
def all_objects
|
34
|
-
@
|
37
|
+
@objects.values
|
38
|
+
end
|
39
|
+
|
40
|
+
def already_loaded_by_unique_key?(config, column_name, id)
|
41
|
+
!find_by_unique_key(config, column_name, id).nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def already_loaded?(config, object)
|
47
|
+
!find_by_primary_key(config, object).nil?
|
35
48
|
end
|
36
49
|
|
37
|
-
|
38
|
-
|
50
|
+
# TODO: Do we have to worry about symbols vs strings for the column_name?
|
51
|
+
def add_to_cache(config, object)
|
52
|
+
# we take a shortcut here assuming that the cache has already been primed with the primary key column
|
53
|
+
# because this method should always be guarded by #already_loaded?
|
54
|
+
column_cache = @cache[config]
|
55
|
+
column_cache.each do |column_name, unique_key_cache|
|
56
|
+
unique_key_cache[object.send(column_name)] = object
|
57
|
+
end
|
58
|
+
object
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_from_cache(config, column_name, value)
|
62
|
+
lookup_hash(config, column_name)[value]
|
63
|
+
end
|
64
|
+
|
65
|
+
# lazily primes the cache
|
66
|
+
# TODO: Do we have to worry about symbols vs strings for the column_name?
|
67
|
+
def lookup_hash(config, column_name)
|
68
|
+
column_cache = @cache[config]
|
69
|
+
if column_cache.nil?
|
70
|
+
column_cache = {}
|
71
|
+
@cache[config] = column_cache
|
72
|
+
end
|
73
|
+
unique_key_cache = column_cache[column_name]
|
74
|
+
if unique_key_cache.nil?
|
75
|
+
unique_key_cache = {}
|
76
|
+
column_cache[column_name] = unique_key_cache
|
77
|
+
@objects[config].each do |object|
|
78
|
+
unique_key_cache[object.send(column_name)] = object
|
79
|
+
end
|
80
|
+
end
|
81
|
+
unique_key_cache
|
39
82
|
end
|
40
83
|
end
|
41
84
|
end
|
@@ -1,19 +1,33 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Vorpal
|
2
4
|
module Util
|
3
5
|
# @private
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
class ArrayHash
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@hash, :each, :empty?, :[]
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@hash = Hash.new([])
|
13
|
+
end
|
14
|
+
|
15
|
+
def append(key, values)
|
16
|
+
if @hash[key].nil? || @hash[key].empty?
|
17
|
+
@hash[key] = []
|
8
18
|
end
|
9
|
-
|
19
|
+
@hash[key].concat(Array(values))
|
10
20
|
end
|
11
21
|
|
12
|
-
def pop
|
13
|
-
key =
|
14
|
-
values =
|
22
|
+
def pop
|
23
|
+
key = @hash.first.first
|
24
|
+
values = @hash.delete(key)
|
15
25
|
[key, values]
|
16
26
|
end
|
27
|
+
|
28
|
+
def values
|
29
|
+
@hash.values.flatten
|
30
|
+
end
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|