vorpal 0.0.6.rc4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,49 +2,52 @@ require 'vorpal/aggregate_repository'
2
2
  require 'vorpal/config_builder'
3
3
 
4
4
  module Vorpal
5
+ module Configuration
5
6
 
6
- module Configuration
7
+ # Configures and creates a {Vorpal::AggregateRepository} instance.
8
+ #
9
+ # @param options [Hash] Global configuration options for the repository instance.
10
+ # @option options [Object] :db_driver (Object that will be used to interact with the DB.)
11
+ # Must be duck-type compatible with {Vorpal::DbDriver}.
12
+ #
13
+ # @return [Vorpal::AggregateRepository] Repository instance.
14
+ def define(options={}, &block)
15
+ master_config = build_config(&block)
16
+ db_driver = options.fetch(:db_driver, DbDriver.new)
17
+ AggregateRepository.new(db_driver, master_config)
18
+ end
7
19
 
8
- # Configures and creates a {Vorpal::AggregateRepository} instance.
9
- #
10
- # @param options [Hash] Global configuration options for the repository instance.
11
- # @option options [Object] :db_driver (Object that will be used to interact with the DB.)
12
- # Must be duck-type compatible with Vorpal::DbDriver.
13
- #
14
- # @return [Vorpal::AggregateRepository] Repository instance.
15
- def define(options={}, &block)
16
- master_config = build_config(&block)
17
- db_driver = options.fetch(:db_driver, DbDriver.new)
18
- AggregateRepository.new(db_driver, master_config)
19
- end
20
-
21
- # Maps a domain class to a relational table.
22
- #
23
- # @param domain_class [Class] Type of the domain model to be mapped
24
- # @param options [Hash] Configure how to map the domain model
25
- # @option options [String] :to (Class with the same name as the domain class with a 'DB' appended.)
26
- # Class of the ActiveRecord object that will map this domain class to the DB.
27
- # @option options [Object] :serializer (map the {ConfigBuilder#fields} directly)
28
- # Object that will convert the domain objects into a hash.
29
- #
30
- # Must have a `(Hash) serialize(Object)` method.
31
- # @option options [Object] :deserializer (map the {ConfigBuilder#fields} directly)
32
- # Object that will set a hash of attribute_names->values onto a new domain
33
- # object.
34
- #
35
- # Must have a `(Object) deserialize(Object, Hash)` method.
36
- def map(domain_class, options={}, &block)
37
- builder = ConfigBuilder.new(domain_class, options)
38
- builder.instance_exec(&block) if block_given?
20
+ # Maps a domain class to a relational table.
21
+ #
22
+ # @param domain_class [Class] Type of the domain model to be mapped
23
+ # @param options [Hash] Configure how to map the domain model
24
+ # @option options [String] :to (Class with the same name as the domain class with a 'DB' appended.)
25
+ # Class of the ActiveRecord object that will map this domain class to the DB.
26
+ # @option options [Object] :serializer (map the {ConfigBuilder#fields} directly)
27
+ # Object that will convert the domain objects into a hash.
28
+ #
29
+ # Must have a `(Hash) serialize(Object)` method.
30
+ # @option options [Object] :deserializer (map the {ConfigBuilder#fields} directly)
31
+ # Object that will set a hash of attribute_names->values onto a new domain
32
+ # object.
33
+ #
34
+ # Must have a `(Object) deserialize(Object, Hash)` method.
35
+ def map(domain_class, options={}, &block)
36
+ @class_configs << build_class_config(domain_class, options, &block)
37
+ end
39
38
 
40
- @class_configs << builder.build
41
- end
39
+ # @private
40
+ def build_class_config(domain_class, options={}, &block)
41
+ builder = ConfigBuilder.new(domain_class, options)
42
+ builder.instance_exec(&block) if block_given?
43
+ builder.build
44
+ end
42
45
 
43
- # @private
44
- def build_config(&block)
45
- @class_configs = []
46
- self.instance_exec(&block)
47
- MasterConfig.new(@class_configs)
46
+ # @private
47
+ def build_config(&block)
48
+ @class_configs = []
49
+ self.instance_exec(&block)
50
+ MasterConfig.new(@class_configs)
51
+ end
48
52
  end
49
53
  end
50
- end
@@ -1,4 +1,7 @@
1
1
  module Vorpal
2
+ # Interfaces between the database and Vorpal
3
+ #
4
+ # Currently only works for PostgreSQL.
2
5
  class DbDriver
3
6
  def initialize
4
7
  @sequence_names = {}
@@ -20,20 +23,29 @@ module Vorpal
20
23
  end
21
24
  end
22
25
 
23
- def destroy(class_config, db_objects)
24
- class_config.db_class.delete_all(id: db_objects.map(&:id))
26
+ def destroy(class_config, ids)
27
+ class_config.db_class.delete_all(id: ids)
25
28
  end
26
29
 
30
+ # Loads instances of the given class by primary key.
31
+ #
32
+ # @return [[Object]] An array of entities.
27
33
  def load_by_id(class_config, ids)
28
34
  class_config.db_class.where(id: ids)
29
35
  end
30
36
 
37
+ # Loads instances of the given class whose foreign key has the given value.
38
+ #
39
+ # @return [[Object]] An array of entities.
31
40
  def load_by_foreign_key(class_config, id, foreign_key_info)
32
41
  arel = class_config.db_class.where(foreign_key_info.fk_column => id)
33
42
  arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
34
43
  arel.order(:id).all
35
44
  end
36
45
 
46
+ # Fetches primary key values to be used for new entities.
47
+ #
48
+ # @return [[Integer]] An array of unused primary keys.
37
49
  def get_primary_keys(class_config, count)
38
50
  result = execute("select nextval($1) from generate_series(1,$2);", [sequence_name(class_config), count])
39
51
  result.rows.map(&:first).map(&:to_i)
@@ -3,138 +3,136 @@ require 'vorpal/util/array_hash'
3
3
  require 'vorpal/db_driver'
4
4
 
5
5
  module Vorpal
6
+ # Handles loading of objects from the database.
7
+ #
8
+ # @private
9
+ class DbLoader
10
+ def initialize(only_owned, db_driver)
11
+ @only_owned = only_owned
12
+ @db_driver = db_driver
13
+ end
6
14
 
7
- # Handles loading of objects from the database.
8
- #
9
- # @private
10
- class DbLoader
11
- def initialize(only_owned, db_driver)
12
- @only_owned = only_owned
13
- @db_driver = db_driver
14
- end
15
+ def load_from_db(ids, config)
16
+ @loaded_objects = LoadedObjects.new
17
+ @lookup_instructions = LookupInstructions.new
18
+ @lookup_instructions.lookup_by_id(config, ids)
15
19
 
16
- def load_from_db(ids, config)
17
- @loaded_objects = LoadedObjects.new
18
- @lookup_instructions = LookupInstructions.new
19
- @lookup_instructions.lookup_by_id(config, ids)
20
+ until @lookup_instructions.empty?
21
+ lookup = @lookup_instructions.next_lookup
22
+ new_objects = lookup.load_all(@db_driver)
23
+ @loaded_objects.add(lookup.config, new_objects)
24
+ explore_objects(lookup.config, new_objects)
25
+ end
20
26
 
21
- until @lookup_instructions.empty?
22
- lookup = @lookup_instructions.next_lookup
23
- new_objects = lookup.load_all(@db_driver)
24
- @loaded_objects.add(lookup.config, new_objects)
25
- explore_objects(lookup.config, new_objects)
27
+ @loaded_objects
26
28
  end
27
29
 
28
- @loaded_objects
29
- end
30
-
31
- private
30
+ private
32
31
 
33
- def explore_objects(config, objects_to_explore)
34
- objects_to_explore.each do |db_object|
35
- config.has_manys.each do |has_many_config|
36
- lookup_by_fk(db_object, has_many_config) if explore_association?(has_many_config)
37
- end
32
+ def explore_objects(config, objects_to_explore)
33
+ objects_to_explore.each do |db_object|
34
+ config.has_manys.each do |has_many_config|
35
+ lookup_by_fk(db_object, has_many_config) if explore_association?(has_many_config)
36
+ end
38
37
 
39
- config.has_ones.each do |has_one_config|
40
- lookup_by_fk(db_object, has_one_config) if explore_association?(has_one_config)
41
- end
38
+ config.has_ones.each do |has_one_config|
39
+ lookup_by_fk(db_object, has_one_config) if explore_association?(has_one_config)
40
+ end
42
41
 
43
- config.belongs_tos.each do |belongs_to_config|
44
- lookup_by_id(db_object, belongs_to_config) if explore_association?(belongs_to_config)
42
+ config.belongs_tos.each do |belongs_to_config|
43
+ lookup_by_id(db_object, belongs_to_config) if explore_association?(belongs_to_config)
44
+ end
45
45
  end
46
46
  end
47
- end
48
47
 
49
- def explore_association?(association_config)
50
- !@only_owned || association_config.owned == true
51
- end
48
+ def explore_association?(association_config)
49
+ !@only_owned || association_config.owned == true
50
+ end
52
51
 
53
- def lookup_by_id(db_object, belongs_to_config)
54
- child_config = belongs_to_config.child_config(db_object)
55
- id = belongs_to_config.fk_value(db_object)
56
- return if @loaded_objects.id_lookup_done?(child_config, id)
57
- @lookup_instructions.lookup_by_id(child_config, id)
58
- end
52
+ def lookup_by_id(db_object, belongs_to_config)
53
+ child_config = belongs_to_config.child_config(db_object)
54
+ id = belongs_to_config.fk_value(db_object)
55
+ return if id.nil? || @loaded_objects.already_loaded?(child_config, id)
56
+ @lookup_instructions.lookup_by_id(child_config, id)
57
+ end
59
58
 
60
- def lookup_by_fk(db_object, has_some_config)
61
- child_config = has_some_config.child_config
62
- fk_info = has_some_config.foreign_key_info
63
- fk_value = db_object.id
64
- @lookup_instructions.lookup_by_fk(child_config, fk_info, fk_value)
59
+ def lookup_by_fk(db_object, has_some_config)
60
+ child_config = has_some_config.child_config
61
+ fk_info = has_some_config.foreign_key_info
62
+ fk_value = db_object.id
63
+ @lookup_instructions.lookup_by_fk(child_config, fk_info, fk_value)
64
+ end
65
65
  end
66
- end
67
66
 
68
- # @private
69
- class LookupInstructions
70
- include ArrayHash
71
- def initialize
72
- @lookup_by_id = {}
73
- @lookup_by_fk = {}
74
- end
67
+ # @private
68
+ class LookupInstructions
69
+ include ArrayHash
70
+ def initialize
71
+ @lookup_by_id = {}
72
+ @lookup_by_fk = {}
73
+ end
75
74
 
76
- def lookup_by_id(config, ids)
77
- add_to_hash(@lookup_by_id, config, Array(ids))
78
- end
75
+ def lookup_by_id(config, ids)
76
+ add_to_hash(@lookup_by_id, config, Array(ids))
77
+ end
79
78
 
80
- def lookup_by_fk(config, fk_info, fk_value)
81
- add_to_hash(@lookup_by_fk, [config, fk_info], fk_value)
82
- end
79
+ def lookup_by_fk(config, fk_info, fk_value)
80
+ add_to_hash(@lookup_by_fk, [config, fk_info], fk_value)
81
+ end
83
82
 
84
- def next_lookup
85
- if @lookup_by_id.empty?
86
- pop_fk_lookup
87
- else
88
- pop_id_lookup
83
+ def next_lookup
84
+ if @lookup_by_id.empty?
85
+ pop_fk_lookup
86
+ else
87
+ pop_id_lookup
88
+ end
89
89
  end
90
- end
91
90
 
92
- def empty?
93
- @lookup_by_id.empty? && @lookup_by_fk.empty?
94
- end
91
+ def empty?
92
+ @lookup_by_id.empty? && @lookup_by_fk.empty?
93
+ end
95
94
 
96
- private
95
+ private
97
96
 
98
- def pop_id_lookup
99
- config, ids = pop(@lookup_by_id)
100
- LookupById.new(config, ids)
101
- end
97
+ def pop_id_lookup
98
+ config, ids = pop(@lookup_by_id)
99
+ LookupById.new(config, ids)
100
+ end
102
101
 
103
- def pop_fk_lookup
104
- key, fk_values = pop(@lookup_by_fk)
105
- config = key.first
106
- fk_info = key.last
107
- LookupByFk.new(config, fk_info, fk_values)
102
+ def pop_fk_lookup
103
+ key, fk_values = pop(@lookup_by_fk)
104
+ config = key.first
105
+ fk_info = key.last
106
+ LookupByFk.new(config, fk_info, fk_values)
107
+ end
108
108
  end
109
- end
110
109
 
111
- # @private
112
- class LookupById
113
- attr_reader :config
114
- def initialize(config, ids)
115
- @config = config
116
- @ids = ids
117
- end
110
+ # @private
111
+ class LookupById
112
+ attr_reader :config
113
+ def initialize(config, ids)
114
+ @config = config
115
+ @ids = ids
116
+ end
118
117
 
119
- def load_all(db_driver)
120
- return [] if @ids.empty?
121
- db_driver.load_by_id(@config, @ids)
118
+ def load_all(db_driver)
119
+ return [] if @ids.empty?
120
+ db_driver.load_by_id(@config, @ids)
121
+ end
122
122
  end
123
- end
124
123
 
125
- # @private
126
- class LookupByFk
127
- attr_reader :config
128
- def initialize(config, fk_info, fk_values)
129
- @config = config
130
- @fk_info = fk_info
131
- @fk_values = fk_values
132
- end
124
+ # @private
125
+ class LookupByFk
126
+ attr_reader :config
127
+ def initialize(config, fk_info, fk_values)
128
+ @config = config
129
+ @fk_info = fk_info
130
+ @fk_values = fk_values
131
+ end
133
132
 
134
- def load_all(db_driver)
135
- return [] if @fk_values.empty?
136
- db_driver.load_by_foreign_key(@config, @fk_values, @fk_info)
133
+ def load_all(db_driver)
134
+ return [] if @fk_values.empty?
135
+ db_driver.load_by_foreign_key(@config, @fk_values, @fk_info)
136
+ end
137
137
  end
138
138
  end
139
-
140
- end
@@ -1,34 +1,34 @@
1
1
  module Vorpal
2
- class IdentityMap
3
- def initialize
4
- @entities = {}
5
- end
2
+ class IdentityMap
3
+ def initialize
4
+ @entities = {}
5
+ end
6
6
 
7
- def get(key_object)
8
- @entities[key(key_object)]
9
- end
7
+ def get(key_object)
8
+ @entities[key(key_object)]
9
+ end
10
10
 
11
- def set(key_object, object)
12
- @entities[key(key_object)] = object
13
- end
11
+ def set(key_object, object)
12
+ @entities[key(key_object)] = object
13
+ end
14
14
 
15
- def get_and_set(key_object)
16
- object = get(key_object)
17
- object = yield if object.nil?
18
- set(key_object, object)
19
- object
20
- end
15
+ def get_and_set(key_object)
16
+ object = get(key_object)
17
+ object = yield if object.nil?
18
+ set(key_object, object)
19
+ object
20
+ end
21
21
 
22
- def map(key_objects)
23
- key_objects.map { |k| @entities[key(k)] }
24
- end
22
+ def map(key_objects)
23
+ key_objects.map { |k| @entities[key(k)] }
24
+ end
25
25
 
26
- private
26
+ private
27
27
 
28
- def key(key_object)
29
- return nil unless key_object
30
- raise "Cannot put entity '#{key_object.inspect}' into IdentityMap without an id." if key_object.id.nil?
31
- [key_object.id, key_object.class.name]
28
+ def key(key_object)
29
+ return nil unless key_object
30
+ raise "Cannot put entity '#{key_object.inspect}' into IdentityMap without an id." if key_object.id.nil?
31
+ [key_object.id, key_object.class.name]
32
+ end
32
33
  end
33
34
  end
34
- end
@@ -3,38 +3,39 @@ require 'forwardable'
3
3
 
4
4
  module Vorpal
5
5
 
6
- # @private
7
- class LoadedObjects
8
- include ArrayHash
9
- extend Forwardable
10
- include Enumerable
11
-
12
- attr_reader :objects
13
- def_delegators :objects, :each
14
-
15
- def initialize
16
- @objects = Hash.new([])
17
- @objects_by_id = Hash.new
18
- end
19
-
20
- def add(config, objects)
21
- add_to_hash(@objects, config, objects)
6
+ # @private
7
+ class LoadedObjects
8
+ include ArrayHash
9
+ extend Forwardable
10
+ include Enumerable
11
+
12
+ attr_reader :objects
13
+ def_delegators :objects, :each
14
+
15
+ def initialize
16
+ @objects = Hash.new([])
17
+ @objects_by_id = Hash.new
18
+ end
22
19
 
23
- objects.each do |object|
24
- @objects_by_id[[config.domain_class.name, object.id]] = object
20
+ def add(config, objects)
21
+ objects_to_add = objects.map do |object|
22
+ if !already_loaded?(config, object.id)
23
+ @objects_by_id[[config.domain_class.name, object.id]] = object
24
+ end
25
+ end
26
+ add_to_hash(@objects, config, objects_to_add.compact)
25
27
  end
26
- end
27
28
 
28
- def find_by_id(config, id)
29
- @objects_by_id[[config.domain_class.name, id]]
30
- end
29
+ def find_by_id(config, id)
30
+ @objects_by_id[[config.domain_class.name, id]]
31
+ end
31
32
 
32
- def all_objects
33
- @objects_by_id.values
34
- end
33
+ def all_objects
34
+ @objects_by_id.values
35
+ end
35
36
 
36
- def id_lookup_done?(config, id)
37
- !find_by_id(config, id).nil?
37
+ def already_loaded?(config, id)
38
+ !find_by_id(config, id).nil?
39
+ end
38
40
  end
39
41
  end
40
- end