hold 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,26 @@
1
- module Hold::Sequel
2
- class QueryArrayCell
3
- include Hold::ArrayCell
1
+ module Hold
2
+ module Sequel
3
+ # Query an array cell
4
+ class QueryArrayCell
5
+ include Hold::ArrayCell
4
6
 
5
- def initialize(repo, *query_args, &query_block)
6
- @repo, @query_block = repo, query_block
7
- end
7
+ def initialize(repo, *_query_args, &query_block)
8
+ @repo, @query_block = repo, query_block
9
+ end
8
10
 
9
- def get(properties=nil)
10
- @repo.query(properties, &@query_block).to_a
11
- end
11
+ def get(properties = nil)
12
+ @repo.query(properties, &@query_block).to_a
13
+ end
12
14
 
13
- def get_slice(start, length, properties=nil)
14
- @repo.query(properties, &@query_block).to_a(true)[start, length]
15
- end
15
+ def slice(start, length, properties = nil)
16
+ @repo.query(properties, &@query_block).to_a(true)[start, length]
17
+ end
18
+ alias_method :get_slice, :slice
16
19
 
17
- def get_length
18
- Query.new(@repo, [], &@query_block).to_a(true).length
20
+ def length
21
+ Query.new(@repo, [], &@query_block).to_a(true).length
22
+ end
23
+ alias_method :get_length, :length
19
24
  end
20
25
  end
21
26
  end
@@ -1,28 +1,32 @@
1
- # If you want to observe events on a Hold::Sequel::IdentitySetRepository
2
- # you need to implement this interface. Stubs supplied here to save you some
3
- # boilerplate in case you only care about certain events.
4
- #
5
- # The callback methods correspond to their counterparts on
6
- # Hold::Sequel::IdentityHashRepository but with an added argument
7
- # passing the repository instance.
8
- #
9
- # TODO: generalise this to SetRepositories in general
10
- module Hold::Sequel::RepositoryObserver
11
- def pre_insert(repo, entity)
12
- end
1
+ module Hold
2
+ module Sequel
3
+ # If you want to observe events on a Hold::Sequel::IdentitySetRepository
4
+ # you need to implement this interface. Stubs supplied here to save you some
5
+ # boilerplate in case you only care about certain events.
6
+ #
7
+ # The callback methods correspond to their counterparts on
8
+ # Hold::Sequel::IdentityHashRepository but with an added argument
9
+ # passing the repository instance.
10
+ #
11
+ # TODO: generalise this to SetRepositories in general
12
+ module RepositoryObserver
13
+ def pre_insert(_repo, _entity)
14
+ end
13
15
 
14
- def post_insert(repo, entity, insert_rows, insert_id)
15
- end
16
+ def post_insert(_repo, _entity, _insert_rows, _insert_id)
17
+ end
16
18
 
17
- def pre_update(repo, entity, update_entity)
18
- end
19
+ def pre_update(_repo, _entity, _update_entity)
20
+ end
19
21
 
20
- def post_update(repo, entity, update_entity, update_rows)
21
- end
22
+ def post_update(_repo, _entity, _update_entity, _update_rows)
23
+ end
22
24
 
23
- def pre_delete(repo, entity)
24
- end
25
+ def pre_delete(_repo, _entity)
26
+ end
25
27
 
26
- def post_delete(repo, entity)
28
+ def post_delete(_repo, _entity)
29
+ end
30
+ end
27
31
  end
28
32
  end
@@ -1,117 +1,163 @@
1
- module Hold::Sequel
2
- # Subclass of Sequel::IdentitySetRepository which adds support for a polymorphic type column which
3
- # is used to persist the class of the model.
4
- class IdentitySetRepository::WithPolymorphicTypeColumn < IdentitySetRepository
5
-
6
- class << self
7
- def type_column; @type_column ||= superclass.type_column; end
8
- def type_column_table; @type_column_table ||= superclass.type_column_table; end
9
- def class_to_type_mapping; @class_to_type_mapping ||= (superclass.class_to_type_mapping if superclass < IdentitySetRepository::WithPolymorphicTypeColumn); end
10
- def type_to_class_mapping; @type_to_class_mapping ||= (superclass.type_to_class_mapping if superclass < IdentitySetRepository::WithPolymorphicTypeColumn); end
11
- def restricted_to_types; @restricted_to_types ||= (superclass.restricted_to_types if superclass < IdentitySetRepository::WithPolymorphicTypeColumn); end
12
-
13
- def supported_classes
14
- if restricted_to_types
15
- type_to_class_mapping.values_at(*restricted_to_types)
16
- else
17
- class_to_type_mapping.keys
18
- end
19
- end
1
+ module Hold
2
+ module Sequel
3
+ class IdentitySetRepository
4
+ # Subclass of Sequel::IdentitySetRepository which adds support for a
5
+ # polymorphic type column which is used to persist the class of the model.
6
+ class WithPolymorphicTypeColumn < IdentitySetRepository
7
+ class << self
8
+ def type_column
9
+ @type_column ||= superclass.type_column
10
+ end
20
11
 
21
- private
12
+ def type_column_table
13
+ @type_column_table ||= superclass.type_column_table
14
+ end
22
15
 
23
- def set_type_column(column, table, mapping=nil)
24
- unless superclass == IdentitySetRepository::WithPolymorphicTypeColumn
25
- raise "set_type_column only on the topmost IdentitySetRepository::WithPolymorphicTypeColumn subclass"
26
- end
27
- table, mapping = tables.first.first, table if table.is_a?(Hash)
28
- @type_column = column
29
- @type_column_table = table
30
- @class_to_type_mapping = mapping
31
- @type_to_class_mapping = mapping.invert
32
- end
16
+ def class_to_type_mapping
17
+ @class_to_type_mapping ||=
18
+ if superclass < WithPolymorphicTypeColumn
19
+ superclass.class_to_type_mapping
20
+ end
21
+ end
22
+
23
+ def type_to_class_mapping
24
+ @type_to_class_mapping ||=
25
+ if superclass < WithPolymorphicTypeColumn
26
+ superclass.type_to_class_mapping
27
+ end
28
+ end
29
+
30
+ def restricted_to_types
31
+ @restricted_to_types ||=
32
+ if superclass < WithPolymorphicTypeColumn
33
+ superclass.restricted_to_types
34
+ end
35
+ end
36
+
37
+ def supported_classes
38
+ if restricted_to_types
39
+ type_to_class_mapping.values_at(*restricted_to_types)
40
+ else
41
+ class_to_type_mapping.keys
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def set_type_column(column, table, mapping = nil)
48
+ unless superclass == WithPolymorphicTypeColumn
49
+ fail 'set_type_column only on the topmost subclass'
50
+ end
33
51
 
34
- def set_model_class(model_class)
35
- @model_class = model_class
36
- raise "set_type_column before set_model_class" unless class_to_type_mapping
37
- klasses = class_to_type_mapping.keys.select {|klass| klass <= model_class}
38
- @restricted_to_types = if klasses.size < @class_to_type_mapping.size
39
- class_to_type_mapping.values_at(*klasses)
52
+ table, mapping = tables.first.first, table if table.is_a?(Hash)
53
+ @type_column = column
54
+ @type_column_table = table
55
+ @class_to_type_mapping = mapping
56
+ @type_to_class_mapping = mapping.invert
57
+ end
58
+
59
+ def set_model_class(model_class)
60
+ @model_class = model_class
61
+ unless class_to_type_mapping
62
+ fail 'set_type_column before set_model_class'
63
+ end
64
+
65
+ klasses = class_to_type_mapping.keys
66
+ .select { |klass| klass <= model_class }
67
+
68
+ @restricted_to_types = if klasses.size < @class_to_type_mapping.size
69
+ class_to_type_mapping.values_at(*klasses)
70
+ end
71
+ end
40
72
  end
41
- end
42
- end
43
73
 
44
- def initialize(db)
45
- super
46
- @qualified_type_column = Sequel::SQL::QualifiedIdentifier.new(self.class.type_column_table, self.class.type_column)
47
- @aliased_type_column = Sequel::SQL::AliasedExpression.new(@qualified_type_column, :type)
74
+ def initialize(db)
75
+ super
76
+ @qualified_type_column =
77
+ ::Sequel::SQL::QualifiedIdentifier.new(self.class.type_column_table,
78
+ self.class.type_column)
48
79
 
49
- @restricted_to_types = self.class.restricted_to_types
50
- @tables_restricting_type = {}
51
- self.class.tables.each do |name, options|
52
- @tables_restricting_type[name] = true if options[:restricts_type]
53
- end
54
- end
80
+ @aliased_type_column =
81
+ ::Sequel::SQL::AliasedExpression.new(@qualified_type_column, :type)
55
82
 
56
- def can_get_class?(model_class)
57
- self.class.supported_classes.include?(model_class)
58
- end
83
+ @restricted_to_types = self.class.restricted_to_types
84
+ @tables_restricting_type = {}
85
+ self.class.tables.each do |name, options|
86
+ @tables_restricting_type[name] = true if options[:restricts_type]
87
+ end
88
+ end
59
89
 
60
- def can_set_class?(model_class)
61
- self.class.supported_classes.include?(model_class)
62
- end
90
+ def can_get_class?(model_class)
91
+ self.class.supported_classes.include?(model_class)
92
+ end
63
93
 
64
- def construct_entity(property_hash, row=nil)
65
- type_value = row && row[:type] or return super
66
- klass = self.class.type_to_class_mapping[type_value || @restricted_to_types.first] or
67
- raise "WithPolymorphicTypeColumn: type column value #{type_value} not found in mapping"
68
- klass.new(property_hash)
69
- end
94
+ def can_set_class?(model_class)
95
+ self.class.supported_classes.include?(model_class)
96
+ end
70
97
 
71
- # This optimisation has to be turned off for polymorphic repositories, since even if
72
- # we know the ID, we have to query the db to find out the appropriate class to construct
73
- # the object as.
74
- def can_construct_from_id_alone?(properties)
75
- super && @restricted_to_types && @restricted_to_types.length == 1
76
- end
98
+ def construct_entity(property_hash, row = nil)
99
+ type_value = row && row[:type] || (return super)
100
+ klass =
101
+ self.class.type_to_class_mapping[type_value] ||
102
+ (fail "WithPolymorphicTypeColumn: type column value #{type_value}" \
103
+ ' not found in mapping')
104
+ klass.new(property_hash)
105
+ end
77
106
 
78
- # ensure we select the type column in addition to any columns for mapped properties,
79
- # so we know which class to instantiate for each row.
80
- #
81
- # If we're restricted to only one class, we don't need to select the type column
82
- def columns_aliases_and_tables_for_properties(properties)
83
- columns_by_property, aliased_columns, tables = super
84
- unless @restricted_to_types && @restricted_to_types.length == 1
85
- aliased_columns << @aliased_type_column
86
- tables << self.class.type_column_table unless tables.include?(self.class.type_column_table)
87
- end
88
- return columns_by_property, aliased_columns, tables
89
- end
107
+ # This optimisation has to be turned off for polymorphic repositories,
108
+ # since even if we know the ID, we have to query the db to find out the
109
+ # appropriate class to construct the object as.
110
+ def can_construct_from_id_alone?(properties)
111
+ super && @restricted_to_types && @restricted_to_types.length == 1
112
+ end
90
113
 
91
- # Where 'restricted_to_types' has been set, ensure we add a filter to the where
92
- # clause restricting to rows with the allowed class (or classes).
93
- #
94
- # Except, where one of the tables used is specified in this repo's config as
95
- # :restricts_type => true, this is taken to mean that (inner) joining to this table
96
- # effectively acts as this repo's restricted_to_types restriction. hence no additional
97
- # where clause is needed in order to do this. Helps with Class Table Inheritance.
98
- def dataset_to_select_tables(*tables)
99
- if @restricted_to_types && !@tables_restricting_type.values_at(*tables).any?
100
- super.filter(@qualified_type_column => @restricted_to_types)
101
- else
102
- super
103
- end
104
- end
114
+ # ensure we select the type column in addition to any columns for mapped
115
+ # properties, so we know which class to instantiate for each row.
116
+ #
117
+ # If we're restricted to only one class, we don't need to select the
118
+ # type column
119
+ def columns_aliases_and_tables_for_properties(properties)
120
+ columns_by_property, aliased_columns, tables = super
121
+ unless @restricted_to_types && @restricted_to_types.length == 1
122
+ aliased_columns << @aliased_type_column
123
+ unless tables.include?(self.class.type_column_table)
124
+ tables << self.class.type_column_table
125
+ end
126
+ end
127
+ [columns_by_property, aliased_columns, tables]
128
+ end
129
+
130
+ # Where 'restricted_to_types' has been set, ensure we add a filter to
131
+ # the where clause restricting to rows with the allowed class (or
132
+ # classes).
133
+ #
134
+ # Except, where one of the tables used is specified in this repo's
135
+ # config as :restricts_type => true, this is taken to mean that (inner)
136
+ # joining to this table effectively acts as this repo's
137
+ # restricted_to_types restriction. hence no additional where clause is
138
+ # needed in order to do this. Helps with Class Table Inheritance.
139
+ def dataset_to_select_tables(*tables)
140
+ if @restricted_to_types &&
141
+ !@tables_restricting_type.values_at(*tables).any?
142
+ super.filter(@qualified_type_column => @restricted_to_types)
143
+ else
144
+ super
145
+ end
146
+ end
105
147
 
106
- private
148
+ private
107
149
 
108
- def insert_row_for_entity(entity, table, id=nil)
109
- row = super
110
- if table == self.class.type_column_table
111
- row[self.class.type_column] = self.class.class_to_type_mapping[entity.class] or
112
- raise "WithPolymorphicTypeColumn: class #{entity.class} not found in mapping"
150
+ def insert_row_for_entity(entity, table, id = nil)
151
+ row = super
152
+ if table == self.class.type_column_table
153
+ row[self.class.type_column] =
154
+ self.class .class_to_type_mapping[entity.class] ||
155
+ (fail "WithPolymorphicTypeColumn: class #{entity.class} not" \
156
+ ' found in mapping')
157
+ end
158
+ row
113
159
  end
114
- row
115
160
  end
161
+ end
116
162
  end
117
163
  end
@@ -1,106 +1,4 @@
1
1
  require 'hold/interfaces'
2
2
 
3
- module Hold
4
-
5
- module Serialized; end
6
-
7
- # A repository which caches serialized versions of an entity in a string-based key/value cache.
8
- #
9
- # Wraps a string-based HashRepository, and requires a serializer responding to 'serialize' and
10
- # 'deserialize'.
11
- #
12
- # May optionally have a 'key_prefix', which is a prefixed namespace added to the cache keys
13
- # before getting/setting the serialized values in the underlying cache.
14
- class Serialized::HashRepository
15
- include Hold::HashRepository
16
-
17
- attr_reader :cache, :serializer, :key_prefix
18
-
19
- def initialize(cache, serializer, key_prefix=nil)
20
- @cache = cache
21
- @serializer = serializer
22
- @key_prefix = key_prefix
23
- end
24
-
25
- def cache_key(key)
26
- @key_prefix ? @key_prefix + key.to_s : key.to_s
27
- end
28
-
29
- def set_with_key(key, entity)
30
- @cache.set_with_key(cache_key(key), @serializer.serialize(entity))
31
- end
32
-
33
- def get_with_key(key)
34
- json = @cache.get_with_key(cache_key(key)) and @serializer.deserialize(json)
35
- end
36
-
37
- def get_many_with_keys(keys)
38
- jsons = @cache.get_many_with_keys(*keys.map {|key| cache_key(key)})
39
- jsons.map {|json| json && @serializer.deserialize(json)}
40
- end
41
-
42
- def has_key?(key)
43
- @cache.has_key?(cache_key(key))
44
- end
45
- alias_method :key?, :has_key?
46
-
47
- def clear_key(key)
48
- @cache.clear_key(cache_key(key))
49
- end
50
- end
51
-
52
- class Serialized::IdentitySetRepository
53
- include Hold::IdentitySetRepository
54
-
55
- attr_reader :cache, :serializer, :key_prefix
56
-
57
- def initialize(cache, serializer, key_prefix=nil)
58
- @cache = cache
59
- @serializer = serializer
60
- @key_prefix = key_prefix
61
- end
62
-
63
- def cache_key(key)
64
- @key_prefix ? @key_prefix + key.to_s : key.to_s
65
- end
66
-
67
- def allocates_ids?
68
- false
69
- end
70
-
71
- def store(object)
72
- id = object.id or raise MissingIdentity
73
- @cache.set_with_key(cache_key(id), @serializer.serialize(object))
74
- object
75
- end
76
-
77
- def delete(object)
78
- id = object.id or raise MissingIdentity
79
- delete_id(id)
80
- end
81
-
82
- def contains?(object)
83
- id = object.id or raise MissingIdentity
84
- contains_id?(id)
85
- end
86
-
87
- def get_by_id(id)
88
- json = @cache.get_with_key(cache_key(id))
89
- if json
90
- string_hash = @serializer.deserialize(json)
91
- string_hash.inject({}) do |memo, (k,v)|
92
- memo[k.to_sym] = v
93
- memo
94
- end
95
- end
96
- end
97
-
98
- def delete_id(id)
99
- @cache.clear_key(cache_key(id))
100
- end
101
-
102
- def contains_id?(id)
103
- @cache.has_key?(cache_key(id))
104
- end
105
- end
106
- end
3
+ require 'hold/serialized/hash_repository'
4
+ require 'hold/serialized/identity_set_repository'