vorpal 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.
@@ -0,0 +1,274 @@
1
+ require 'vorpal/hash_initialization'
2
+
3
+ module Vorpal
4
+
5
+ # @private
6
+ class MasterConfig
7
+ def initialize(class_configs)
8
+ @class_configs = class_configs
9
+ initialize_association_configs
10
+ end
11
+
12
+ def config_for(clazz)
13
+ @class_configs.detect { |conf| conf.domain_class == clazz }
14
+ end
15
+
16
+ def config_for_db(clazz)
17
+ @class_configs.detect { |conf| conf.db_class == clazz }
18
+ end
19
+
20
+ private
21
+
22
+ def initialize_association_configs
23
+ @class_configs.each do |config|
24
+ (config.has_ones + config.has_manys).each do |association_config|
25
+ association_config.init_relational_association(
26
+ config_for(association_config.child_class),
27
+ config
28
+ )
29
+ end
30
+ config.belongs_tos.each do |association_config|
31
+ association_config.init_relational_association(
32
+ association_config.child_classes.map(&method(:config_for)),
33
+ config
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # @private
41
+ class ClassConfig
42
+ attr_reader :serializer, :deserializer, :domain_class, :table_name
43
+ attr_accessor :has_manys, :belongs_tos, :has_ones
44
+
45
+ def initialize(attrs)
46
+ @has_manys = []
47
+ @belongs_tos = []
48
+ @has_ones = []
49
+
50
+ attrs.each do |k,v|
51
+ instance_variable_set("@#{k}", v)
52
+ end
53
+ end
54
+
55
+ def get_primary_keys(count)
56
+ result = ActiveRecord::Base.connection.execute("select nextval('#{sequence_name}') from generate_series(1,#{count});")
57
+ result.column_values(0).map(&:to_i)
58
+ end
59
+
60
+ def db_class
61
+ @db_class ||= ActiveRecord::Base.descendants.detect { |ar| ar.table_name == table_name }
62
+ end
63
+
64
+ def find_in_db(object)
65
+ db_class.find(object.id)
66
+ end
67
+
68
+ def load_by_id(id)
69
+ db_class.where(id: id).first
70
+ end
71
+
72
+ def load_by_foreign_key(id, foreign_key_info)
73
+ arel = db_class.where(foreign_key_info.fk_column => id)
74
+ arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
75
+ arel.order(:id).all
76
+ end
77
+
78
+ def destroy(db_object)
79
+ db_object.destroy
80
+ end
81
+
82
+ def save(db_object)
83
+ db_object.save!
84
+ end
85
+
86
+ def build_db_object(attributes)
87
+ db_class.new(attributes)
88
+ end
89
+
90
+ def set_db_object_attributes(db_object, attributes)
91
+ db_object.attributes = attributes
92
+ end
93
+
94
+ def get_db_object_attributes(db_object)
95
+ db_object.attributes.symbolize_keys
96
+ end
97
+
98
+ def serialization_required?
99
+ !(domain_class < ActiveRecord::Base)
100
+ end
101
+
102
+ def serialize(object)
103
+ serializer.serialize(object)
104
+ end
105
+
106
+ def deserialize(db_object)
107
+ attributes = get_db_object_attributes(db_object)
108
+ serialization_required? ? deserializer.deserialize(domain_class.new, attributes) : db_object
109
+ end
110
+
111
+ def set_field(db_object, field, value)
112
+ db_object.send("#{field}=", value)
113
+ end
114
+
115
+ def get_field(db_object, field)
116
+ db_object.send(field)
117
+ end
118
+
119
+ private
120
+
121
+ def sequence_name
122
+ "#{table_name}_id_seq"
123
+ end
124
+ end
125
+
126
+ # @private
127
+ class ForeignKeyInfo
128
+ attr_reader :fk_column, :fk_type_column, :fk_type, :polymorphic
129
+
130
+ def initialize(fk_column, fk_type_column, fk_type, polymorphic)
131
+ @fk_column = fk_column
132
+ @fk_type_column = fk_type_column
133
+ @fk_type = fk_type
134
+ @polymorphic = polymorphic
135
+ end
136
+
137
+ def polymorphic?
138
+ @polymorphic
139
+ end
140
+ end
141
+
142
+ # @private
143
+ # Object associations:
144
+ # - All object associations are uni-directional
145
+ # - The end that holds the association is the 'Parent' and the end that
146
+ # is refered to is the 'Child' or 'Children'
147
+ #
148
+ # Relational associations:
149
+ # - Local end: has FK
150
+ # - Remote end: has no FK
151
+ #
152
+ class RelationalAssociation
153
+ include HashInitialization
154
+ attr_reader :fk, :fk_type, :local_config, :remote_configs
155
+
156
+ # Can't pass in a remote db model for last param because when saving we only have
157
+ # a db model if the model is part of the aggregate and not just referenced by the
158
+ # aggregate
159
+ def set_foreign_key(local_db_model, remote_model)
160
+ local_config.set_field(local_db_model, fk, remote_model.try(:id))
161
+ local_config.set_field(local_db_model, fk_type, remote_model.class.name) if polymorphic?
162
+ end
163
+
164
+ def load_locals(remote_db_model)
165
+ id = remote_db_model.id
166
+ # TODO: this method should probably be able to determine the config for the remote models
167
+ raise "Only supports having one remote configuration when navigating from the remote side to the local side of an association." if remote_configs.size != 1
168
+ remote_config = remote_configs.first
169
+ local_config.load_by_foreign_key(id, foreign_key_info(remote_config))
170
+ end
171
+
172
+ def load_remote(local_db_model)
173
+ remote_config = polymorphic? ? remote_config_for_local_db_object(local_db_model) : remote_configs.first
174
+ remote_config.load_by_id(get_foreign_key(local_db_model))
175
+ end
176
+
177
+ private
178
+
179
+ def polymorphic?
180
+ !fk_type.nil?
181
+ end
182
+
183
+ def foreign_key_info(class_config)
184
+ ForeignKeyInfo.new(fk, fk_type, class_config.domain_class.name, polymorphic?)
185
+ end
186
+
187
+ def remote_config_for_local_db_object(local_db_model)
188
+ class_name = local_config.get_field(local_db_model, fk_type)
189
+ remote_configs.detect { |config| config.domain_class.name == class_name }
190
+ end
191
+
192
+ def get_foreign_key(local_db_model)
193
+ local_config.get_field(local_db_model, fk)
194
+ end
195
+ end
196
+
197
+ # @private
198
+ class HasManyConfig
199
+ include HashInitialization
200
+ attr_reader :name, :owned, :fk, :fk_type, :child_class
201
+
202
+ def init_relational_association(child_config, parent_config)
203
+ @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: child_config, remote_configs: [parent_config])
204
+ end
205
+
206
+ def get_children(parent)
207
+ parent.send(name)
208
+ end
209
+
210
+ def set_children(parent, children)
211
+ parent.send("#{name}=", children)
212
+ end
213
+
214
+ def set_foreign_key(db_child, parent)
215
+ @relational_association.set_foreign_key(db_child, parent)
216
+ end
217
+
218
+ def load_children(db_parent)
219
+ @relational_association.load_locals(db_parent)
220
+ end
221
+ end
222
+
223
+ # @private
224
+ class BelongsToConfig
225
+ include HashInitialization
226
+ attr_reader :name, :owned, :fk, :fk_type, :child_classes
227
+
228
+ def init_relational_association(child_configs, parent_config)
229
+ @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: parent_config, remote_configs: child_configs)
230
+ end
231
+
232
+ def get_child(parent)
233
+ parent.send(name)
234
+ end
235
+
236
+ def set_child(parent, child)
237
+ parent.send("#{name}=", child)
238
+ end
239
+
240
+ def set_foreign_key(db_parent, child)
241
+ @relational_association.set_foreign_key(db_parent, child)
242
+ end
243
+
244
+ def load_child(db_parent)
245
+ @relational_association.load_remote(db_parent)
246
+ end
247
+ end
248
+
249
+ # @private
250
+ class HasOneConfig
251
+ include HashInitialization
252
+ attr_reader :name, :owned, :fk, :fk_type, :child_class
253
+
254
+ def init_relational_association(child_config, parent_config)
255
+ @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: child_config, remote_configs: [parent_config])
256
+ end
257
+
258
+ def get_child(parent)
259
+ parent.send(name)
260
+ end
261
+
262
+ def set_child(parent, child)
263
+ parent.send("#{name}=", child)
264
+ end
265
+
266
+ def set_foreign_key(db_child, parent)
267
+ @relational_association.set_foreign_key(db_child, parent)
268
+ end
269
+
270
+ def load_child(db_parent)
271
+ @relational_association.load_locals(db_parent).first
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,61 @@
1
+ require 'vorpal/aggregate_repository'
2
+ require 'vorpal/config_builder'
3
+
4
+ module Vorpal
5
+ # Allows easy creation of {Vorpal::AggregateRepository}
6
+ # instances.
7
+ #
8
+ # ```ruby
9
+ # repository = Vorpal::Configuration.define do
10
+ # map Tree do
11
+ # fields :name
12
+ # belongs_to :trunk
13
+ # has_many :branches
14
+ # end
15
+ #
16
+ # map Trunk do
17
+ # fields :length
18
+ # has_one :tree
19
+ # end
20
+ #
21
+ # map Branch do
22
+ # fields :length
23
+ # belongs_to :tree
24
+ # end
25
+ # end
26
+ # ```
27
+ module Configuration
28
+ extend self
29
+
30
+ # Configures and creates a {Vorpal::AggregateRepository} instance.
31
+ #
32
+ # @return [Vorpal::AggregateRepository] Repository instance.
33
+ def define(&block)
34
+ @class_configs = []
35
+ self.instance_exec(&block)
36
+ AggregateRepository.new(@class_configs)
37
+ end
38
+
39
+ # Maps a domain class to a relational table.
40
+ #
41
+ # @param domain_class [Class] Type of the domain model to be mapped
42
+ # @param options [Hash] Configure how to map the domain model
43
+ # @option options [String] :table_name (Name of the domain class snake-cased and pluralized.)
44
+ # Name of the relational DB table.
45
+ # @option options [Object] :serializer (map the {ConfigBuilder#fields} directly)
46
+ # Object that will convert the domain objects into a hash.
47
+ #
48
+ # Must have a `(Hash) serialize(Object)` method.
49
+ # @option options [Object] :deserializer (map the {ConfigBuilder#fields} directly)
50
+ # Object that will set a hash of attribute_names->values onto a new domain
51
+ # object.
52
+ #
53
+ # Must have a `(Object) deserialize(Object, Hash)` method.
54
+ def map(domain_class, options={}, &block)
55
+ builder = ConfigBuilder.new(domain_class, options)
56
+ builder.instance_exec(&block) if block_given?
57
+
58
+ @class_configs << builder.build
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,10 @@
1
+ module Vorpal
2
+ # @private
3
+ module HashInitialization
4
+ def initialize(attrs)
5
+ attrs.each do |k,v|
6
+ instance_variable_set("@#{k}", v)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ module Vorpal
2
+ class IdentityMap
3
+ def initialize
4
+ @entities = {}
5
+ end
6
+
7
+ def get(key_object)
8
+ @entities[key_object]
9
+ end
10
+
11
+ def set(key_object, object)
12
+ @entities[key_object] = object
13
+ end
14
+
15
+ def get_and_set(key_object)
16
+ object = get(key_object)
17
+ object = yield(key_object) if object.nil?
18
+ set(key_object, object)
19
+ object
20
+ end
21
+
22
+ def map(key_objects)
23
+ key_objects.map { |k| @entities[k] }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Vorpal
2
+ VERSION = "0.0.1"
3
+ end
data/lib/vorpal.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "vorpal/version"
2
+
3
+ module Vorpal
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,41 @@
1
+ require 'active_record'
2
+ require 'pg' # or 'mysql2' or 'sqlite3'
3
+
4
+ # Change the following to reflect your database settings
5
+ ActiveRecord::Base.establish_connection(
6
+ adapter: 'postgresql', # or 'mysql2' or 'sqlite3'
7
+ host: 'localhost',
8
+ database: 'vorpal_test',
9
+ username: 'vorpal',
10
+ password: 'pass',
11
+ min_messages: 'error'
12
+ )
13
+
14
+ RSpec.configure do |config|
15
+ # ## Mock Framework
16
+ #
17
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
18
+ #
19
+ # config.mock_with :mocha
20
+ # config.mock_with :flexmock
21
+ # config.mock_with :rr
22
+
23
+ # implements use_transactional_fixtures = true
24
+ # from lib/active_record/fixtures.rb
25
+ # works with Rails 3.2. Probably not with Rails 4
26
+ config.before(:each) do
27
+ connection = ActiveRecord::Base.connection
28
+ connection.increment_open_transactions
29
+ connection.transaction_joinable = false
30
+ connection.begin_db_transaction
31
+ end
32
+
33
+ config.after(:each) do
34
+ connection = ActiveRecord::Base.connection
35
+ if connection.open_transactions != 0
36
+ connection.rollback_db_transaction
37
+ connection.decrement_open_transactions
38
+ end
39
+ ActiveRecord::Base.clear_active_connections!
40
+ end
41
+ end