hold 1.0.2 → 1.0.3

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.
@@ -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'