sequel 3.46.0 → 3.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +96 -0
  3. data/Rakefile +7 -1
  4. data/bin/sequel +6 -4
  5. data/doc/active_record.rdoc +1 -1
  6. data/doc/advanced_associations.rdoc +14 -35
  7. data/doc/association_basics.rdoc +66 -4
  8. data/doc/migration.rdoc +4 -0
  9. data/doc/opening_databases.rdoc +6 -0
  10. data/doc/postgresql.rdoc +302 -0
  11. data/doc/release_notes/3.47.0.txt +270 -0
  12. data/doc/security.rdoc +6 -0
  13. data/lib/sequel/adapters/ibmdb.rb +9 -9
  14. data/lib/sequel/adapters/jdbc.rb +22 -7
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -2
  16. data/lib/sequel/adapters/mock.rb +2 -0
  17. data/lib/sequel/adapters/postgres.rb +44 -13
  18. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  20. data/lib/sequel/adapters/shared/postgres.rb +94 -55
  21. data/lib/sequel/adapters/shared/sqlite.rb +3 -1
  22. data/lib/sequel/adapters/sqlite.rb +2 -2
  23. data/lib/sequel/adapters/utils/pg_types.rb +1 -14
  24. data/lib/sequel/adapters/utils/split_alter_table.rb +3 -3
  25. data/lib/sequel/connection_pool/threaded.rb +1 -1
  26. data/lib/sequel/core.rb +1 -1
  27. data/lib/sequel/database/connecting.rb +2 -2
  28. data/lib/sequel/database/features.rb +5 -0
  29. data/lib/sequel/database/misc.rb +47 -5
  30. data/lib/sequel/database/query.rb +2 -2
  31. data/lib/sequel/dataset/actions.rb +4 -2
  32. data/lib/sequel/dataset/misc.rb +1 -1
  33. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  34. data/lib/sequel/dataset/query.rb +8 -6
  35. data/lib/sequel/dataset/sql.rb +8 -6
  36. data/lib/sequel/extensions/constraint_validations.rb +5 -2
  37. data/lib/sequel/extensions/migration.rb +10 -8
  38. data/lib/sequel/extensions/pagination.rb +3 -0
  39. data/lib/sequel/extensions/pg_array.rb +85 -25
  40. data/lib/sequel/extensions/pg_hstore.rb +8 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +4 -1
  42. data/lib/sequel/extensions/pg_inet.rb +16 -13
  43. data/lib/sequel/extensions/pg_interval.rb +6 -2
  44. data/lib/sequel/extensions/pg_json.rb +18 -11
  45. data/lib/sequel/extensions/pg_range.rb +17 -2
  46. data/lib/sequel/extensions/pg_range_ops.rb +7 -5
  47. data/lib/sequel/extensions/pg_row.rb +29 -12
  48. data/lib/sequel/extensions/pretty_table.rb +3 -0
  49. data/lib/sequel/extensions/query.rb +3 -0
  50. data/lib/sequel/extensions/schema_caching.rb +2 -0
  51. data/lib/sequel/extensions/schema_dumper.rb +3 -1
  52. data/lib/sequel/extensions/select_remove.rb +3 -0
  53. data/lib/sequel/model.rb +8 -2
  54. data/lib/sequel/model/associations.rb +39 -27
  55. data/lib/sequel/model/base.rb +99 -38
  56. data/lib/sequel/model/plugins.rb +25 -0
  57. data/lib/sequel/plugins/association_autoreloading.rb +27 -22
  58. data/lib/sequel/plugins/association_dependencies.rb +1 -7
  59. data/lib/sequel/plugins/auto_validations.rb +110 -0
  60. data/lib/sequel/plugins/boolean_readers.rb +1 -6
  61. data/lib/sequel/plugins/caching.rb +6 -13
  62. data/lib/sequel/plugins/class_table_inheritance.rb +1 -0
  63. data/lib/sequel/plugins/composition.rb +14 -7
  64. data/lib/sequel/plugins/constraint_validations.rb +2 -13
  65. data/lib/sequel/plugins/defaults_setter.rb +1 -6
  66. data/lib/sequel/plugins/dirty.rb +8 -0
  67. data/lib/sequel/plugins/error_splitter.rb +54 -0
  68. data/lib/sequel/plugins/force_encoding.rb +1 -5
  69. data/lib/sequel/plugins/hook_class_methods.rb +1 -6
  70. data/lib/sequel/plugins/input_transformer.rb +79 -0
  71. data/lib/sequel/plugins/instance_filters.rb +7 -1
  72. data/lib/sequel/plugins/instance_hooks.rb +7 -1
  73. data/lib/sequel/plugins/json_serializer.rb +5 -10
  74. data/lib/sequel/plugins/lazy_attributes.rb +20 -7
  75. data/lib/sequel/plugins/list.rb +1 -6
  76. data/lib/sequel/plugins/many_through_many.rb +1 -2
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +23 -39
  78. data/lib/sequel/plugins/optimistic_locking.rb +1 -5
  79. data/lib/sequel/plugins/pg_row.rb +4 -2
  80. data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -7
  81. data/lib/sequel/plugins/prepared_statements.rb +1 -5
  82. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -11
  83. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  84. data/lib/sequel/plugins/serialization.rb +11 -13
  85. data/lib/sequel/plugins/serialization_modification_detection.rb +13 -1
  86. data/lib/sequel/plugins/single_table_inheritance.rb +4 -4
  87. data/lib/sequel/plugins/static_cache.rb +67 -19
  88. data/lib/sequel/plugins/string_stripper.rb +7 -27
  89. data/lib/sequel/plugins/subclasses.rb +3 -5
  90. data/lib/sequel/plugins/tactical_eager_loading.rb +2 -2
  91. data/lib/sequel/plugins/timestamps.rb +2 -7
  92. data/lib/sequel/plugins/touch.rb +5 -8
  93. data/lib/sequel/plugins/tree.rb +1 -6
  94. data/lib/sequel/plugins/typecast_on_load.rb +1 -5
  95. data/lib/sequel/plugins/update_primary_key.rb +26 -14
  96. data/lib/sequel/plugins/validation_class_methods.rb +31 -16
  97. data/lib/sequel/plugins/validation_helpers.rb +50 -26
  98. data/lib/sequel/plugins/xml_serializer.rb +3 -6
  99. data/lib/sequel/sql.rb +1 -1
  100. data/lib/sequel/version.rb +1 -1
  101. data/spec/adapters/postgres_spec.rb +131 -15
  102. data/spec/adapters/sqlite_spec.rb +1 -1
  103. data/spec/core/connection_pool_spec.rb +16 -17
  104. data/spec/core/database_spec.rb +111 -40
  105. data/spec/core/dataset_spec.rb +65 -74
  106. data/spec/core/expression_filters_spec.rb +6 -5
  107. data/spec/core/object_graph_spec.rb +0 -1
  108. data/spec/core/schema_spec.rb +23 -23
  109. data/spec/core/spec_helper.rb +5 -1
  110. data/spec/extensions/association_dependencies_spec.rb +1 -1
  111. data/spec/extensions/association_proxies_spec.rb +1 -1
  112. data/spec/extensions/auto_validations_spec.rb +90 -0
  113. data/spec/extensions/caching_spec.rb +6 -0
  114. data/spec/extensions/class_table_inheritance_spec.rb +8 -1
  115. data/spec/extensions/composition_spec.rb +12 -5
  116. data/spec/extensions/constraint_validations_spec.rb +4 -4
  117. data/spec/extensions/core_refinements_spec.rb +29 -79
  118. data/spec/extensions/dirty_spec.rb +14 -0
  119. data/spec/extensions/error_splitter_spec.rb +18 -0
  120. data/spec/extensions/identity_map_spec.rb +0 -1
  121. data/spec/extensions/input_transformer_spec.rb +54 -0
  122. data/spec/extensions/instance_filters_spec.rb +6 -0
  123. data/spec/extensions/instance_hooks_spec.rb +12 -1
  124. data/spec/extensions/json_serializer_spec.rb +0 -1
  125. data/spec/extensions/lazy_attributes_spec.rb +64 -55
  126. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  127. data/spec/extensions/many_through_many_spec.rb +3 -4
  128. data/spec/extensions/many_to_one_pk_lookup_spec.rb +53 -15
  129. data/spec/extensions/migration_spec.rb +16 -0
  130. data/spec/extensions/null_dataset_spec.rb +1 -1
  131. data/spec/extensions/pg_array_spec.rb +48 -1
  132. data/spec/extensions/pg_hstore_ops_spec.rb +10 -2
  133. data/spec/extensions/pg_hstore_spec.rb +5 -0
  134. data/spec/extensions/pg_inet_spec.rb +5 -0
  135. data/spec/extensions/pg_interval_spec.rb +7 -3
  136. data/spec/extensions/pg_json_spec.rb +6 -1
  137. data/spec/extensions/pg_range_ops_spec.rb +4 -1
  138. data/spec/extensions/pg_range_spec.rb +5 -0
  139. data/spec/extensions/pg_row_plugin_spec.rb +13 -0
  140. data/spec/extensions/pg_row_spec.rb +28 -19
  141. data/spec/extensions/pg_typecast_on_load_spec.rb +6 -1
  142. data/spec/extensions/prepared_statements_associations_spec.rb +1 -1
  143. data/spec/extensions/query_literals_spec.rb +1 -1
  144. data/spec/extensions/rcte_tree_spec.rb +2 -2
  145. data/spec/extensions/schema_spec.rb +2 -2
  146. data/spec/extensions/serialization_modification_detection_spec.rb +8 -0
  147. data/spec/extensions/serialization_spec.rb +15 -1
  148. data/spec/extensions/sharding_spec.rb +1 -1
  149. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  150. data/spec/extensions/static_cache_spec.rb +59 -9
  151. data/spec/extensions/tactical_eager_loading_spec.rb +19 -4
  152. data/spec/extensions/update_primary_key_spec.rb +17 -1
  153. data/spec/extensions/validation_class_methods_spec.rb +25 -0
  154. data/spec/extensions/validation_helpers_spec.rb +59 -3
  155. data/spec/integration/associations_test.rb +5 -5
  156. data/spec/integration/eager_loader_test.rb +32 -63
  157. data/spec/integration/model_test.rb +2 -2
  158. data/spec/integration/plugin_test.rb +88 -56
  159. data/spec/integration/prepared_statement_test.rb +1 -1
  160. data/spec/integration/schema_test.rb +1 -1
  161. data/spec/integration/timezone_test.rb +0 -1
  162. data/spec/integration/transaction_test.rb +0 -1
  163. data/spec/model/association_reflection_spec.rb +1 -1
  164. data/spec/model/associations_spec.rb +106 -84
  165. data/spec/model/base_spec.rb +4 -4
  166. data/spec/model/eager_loading_spec.rb +8 -8
  167. data/spec/model/model_spec.rb +27 -9
  168. data/spec/model/plugins_spec.rb +71 -0
  169. data/spec/model/record_spec.rb +99 -13
  170. metadata +12 -2
@@ -190,7 +190,7 @@ module Sequel
190
190
  opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
191
191
  left_pks = opts[:left_primary_keys] = Array(left_pk)
192
192
  lpkc = opts[:left_primary_key_column] ||= left_pk
193
- lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
193
+ opts[:left_primary_key_columns] ||= Array(lpkc)
194
194
  opts[:dataset] ||= lambda do
195
195
  ds = opts.associated_dataset
196
196
  opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
@@ -267,7 +267,6 @@ module Sequel
267
267
  lpks = ref.qualify(model.table_name, lpks)
268
268
  edges = ref.edges
269
269
  first, rest = edges.first, edges[1..-1]
270
- last = edges.last
271
270
  ds = model.db[first[:table]].select(*Array(ref.qualify(first[:table], first[:right])))
272
271
  rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), :qualify=>:deep, &e[:block])}
273
272
  last_alias = if rest.empty?
@@ -1,26 +1,16 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # This is a fairly simple plugin that modifies the internal association loading logic
3
+ # The ManyToOnePkLookup plugin that modifies the internal association loading logic
4
4
  # for many_to_one associations to use a simple primary key lookup on the associated
5
5
  # class, which is generally faster as it uses mostly static SQL. Additional, if the
6
6
  # associated class is caching primary key lookups, you get the benefit of a cached
7
7
  # lookup.
8
8
  #
9
- # This plugin is generally not as fast as the prepared_statements_associations plugin
10
- # in the case where the model is not caching primary key lookups, however, it is
11
- # probably significantly faster if the model is caching primary key lookups. If
12
- # the prepared_statements_associations plugin has been loaded first, this
13
- # plugin will only use the primary key lookup code if the associated model is
14
- # caching primary key lookups.
15
- #
16
9
  # This plugin attempts to determine cases where the primary key lookup would have
17
- # different results than the regular lookup, and use the regular lookup in that case,
18
- # but it cannot handle all situations correctly, which is why it is not Sequel's
19
- # default behavior.
10
+ # different results than the regular lookup, and use the regular lookup in that case.
11
+ # If you want to explicitly force whether or not to use primary key lookups for
12
+ # a given association, set the :many_to_one_pk_lookup association option.
20
13
  #
21
- # You can disable primary key lookups on a per association basis with this
22
- # plugin using the :many_to_one_pk_lookup=>false association option.
23
- #
24
14
  # Usage:
25
15
  #
26
16
  # # Make all model subclass instances use primary key lookups for many_to_one
@@ -30,37 +20,31 @@ module Sequel
30
20
  # # Do so for just the album class.
31
21
  # Album.plugin :many_to_one_pk_lookup
32
22
  module ManyToOnePkLookup
23
+ module ClassMethods
24
+ # Disable primary key lookup in cases where it will result in a different
25
+ # query than the association query.
26
+ def def_many_to_one(opts)
27
+ if !opts.has_key?(:many_to_one_pk_lookup) &&
28
+ (opts[:dataset] || opts[:conditions] || opts[:block] || opts[:select] ||
29
+ (opts.has_key?(:key) && opts[:key] == nil))
30
+ opts[:many_to_one_pk_lookup] = false
31
+ end
32
+ super
33
+ end
34
+ end
35
+
33
36
  module InstanceMethods
34
37
  private
35
38
 
36
- # If the current association is a fairly simple many_to_one association, use
39
+ # If the current association is a simple many_to_one association, use
37
40
  # a simple primary key lookup on the associated model, which can benefit from
38
41
  # caching if the associated model is using caching.
39
- def _load_associated_object(opts, dynamic_opts)
40
- klass = opts.associated_class
41
- cache_lookup = opts.send(:cached_fetch, :many_to_one_pk_lookup) do
42
- opts[:type] == :many_to_one &&
43
- opts[:key] &&
44
- opts.primary_key == klass.primary_key
45
- end
46
- if cache_lookup &&
47
- !dynamic_opts[:callback] &&
48
- (o = klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk))))
49
- o
50
- else
51
- super
52
- end
53
- end
54
-
55
- # Deal with the situation where the prepared_statements_associations plugin is
56
- # loaded first, by using a primary key lookup for many_to_one associations if
57
- # the associated class is using caching, and using the default code otherwise.
58
- # This is done because the prepared_statements_associations code is probably faster
59
- # than the primary key lookup this plugin uses if the model is not caching lookups,
60
- # but probably slower if the model is caching lookups.
61
42
  def _load_associated_objects(opts, dynamic_opts={})
62
- if opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one && opts.associated_class.respond_to?(:cache_get_pk)
63
- _load_associated_object(opts, dynamic_opts)
43
+ return super unless opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one
44
+ klass = opts.associated_class
45
+ if !dynamic_opts[:callback] &&
46
+ opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == klass.primary_key}
47
+ klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))
64
48
  else
65
49
  super
66
50
  end
@@ -38,11 +38,7 @@ module Sequel
38
38
  # The column holding the version of the lock
39
39
  attr_accessor :lock_column
40
40
 
41
- # Copy the lock_column value into the subclass
42
- def inherited(subclass)
43
- super
44
- subclass.lock_column = lock_column
45
- end
41
+ Plugins.inherited_instance_variables(self, :@lock_column=>nil)
46
42
  end
47
43
 
48
44
  module InstanceMethods
@@ -100,7 +100,9 @@ module Sequel
100
100
  module ClassMethods
101
101
  # Register the model's row type with the database.
102
102
  def register_row_type
103
- db.register_row_type(model.table_name, :converter=>self, :typecaster=>method(:new))
103
+ table = dataset.first_source_table
104
+ db.register_row_type(table, :converter=>self, :typecaster=>method(:new))
105
+ db.instance_variable_get(:@schema_type_classes)[:"pg_row_#{table}"] = self
104
106
  end
105
107
  end
106
108
 
@@ -113,7 +115,7 @@ module Sequel
113
115
  sql << ROW
114
116
  ds.literal_append(sql, values.values_at(*columns))
115
117
  sql << CAST
116
- ds.quote_schema_table_append(sql, model.table_name)
118
+ ds.quote_schema_table_append(sql, model.dataset.first_source_table)
117
119
  end
118
120
  end
119
121
  end
@@ -41,11 +41,7 @@ module Sequel
41
41
  @pg_typecast_on_load_columns.concat(columns)
42
42
  end
43
43
 
44
- # Give the subclass a copy of the columns to typecast on load.
45
- def inherited(subclass)
46
- super
47
- subclass.instance_variable_set(:@pg_typecast_on_load_columns, pg_typecast_on_load_columns.dup)
48
- end
44
+ Plugins.inherited_instance_variables(self, :@pg_typecast_on_load_columns=>:dup)
49
45
  end
50
46
 
51
47
  module InstanceMethods
@@ -53,8 +49,8 @@ module Sequel
53
49
  # object, and use it to convert the value.
54
50
  def set_values(values)
55
51
  model.pg_typecast_on_load_columns.each do |c|
56
- if (v = values[c]).is_a?(String) && (oid = db_schema[c][:oid])
57
- values[c] = db.conversion_procs[oid].call(v)
52
+ if (v = values[c]).is_a?(String) && (oid = db_schema[c][:oid]) && (pr = db.conversion_procs[oid])
53
+ values[c] = pr.call(v)
58
54
  end
59
55
  end
60
56
  super
@@ -36,11 +36,7 @@ module Sequel
36
36
  end
37
37
 
38
38
  module ClassMethods
39
- # Setup the datastructure used to hold the prepared statements in the subclass.
40
- def inherited(subclass)
41
- super
42
- subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
43
- end
39
+ Plugins.inherited_instance_variables(self, :@prepared_statements=>lambda{|v| {:insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{}}})
44
40
 
45
41
  private
46
42
 
@@ -33,17 +33,8 @@ module Sequel
33
33
  # that can be created is 2^N (where N is the number of free columns).
34
34
  attr_reader :prepared_statements_column_defaults
35
35
 
36
- def inherited(subclass)
37
- super
38
- subclass.instance_variable_set(:@prepared_statements_column_defaults, @prepared_statements_column_defaults) if @prepared_statements_column_defaults && !subclass.prepared_statements_column_defaults
39
- end
40
-
41
- # Set the column defaults to use when creating on the subclass.
42
- def set_dataset(*)
43
- x = super
44
- set_prepared_statements_column_defaults
45
- x
46
- end
36
+ Plugins.inherited_instance_variables(self, :@prepared_statements_column_defaults=>:dup)
37
+ Plugins.after_set_dataset(self, :set_prepared_statements_column_defaults)
47
38
 
48
39
  private
49
40
 
@@ -190,7 +190,7 @@ module Sequel
190
190
  elds = elds.select_append(ka) unless elds.opts[:select] == nil
191
191
  elds.all do |obj|
192
192
  opk = obj[prkey]
193
- if in_pm = parent_map.has_key?(opk)
193
+ if parent_map.has_key?(opk)
194
194
  if idm_obj = parent_map[opk]
195
195
  idm_obj.values[ka] = obj.values[ka]
196
196
  obj = idm_obj
@@ -298,7 +298,7 @@ module Sequel
298
298
  end
299
299
 
300
300
  opk = obj[prkey]
301
- if in_pm = parent_map.has_key?(opk)
301
+ if parent_map.has_key?(opk)
302
302
  if idm_obj = parent_map[opk]
303
303
  idm_obj.values[ka] = obj.values[ka]
304
304
  obj = idm_obj
@@ -30,11 +30,11 @@ module Sequel
30
30
  #
31
31
  # == Example
32
32
  #
33
- # require 'sequel'
34
- # # Require json, as the plugin doesn't require it for you.
33
+ # # Require json if you plan to use it, as the plugin doesn't require it for you.
35
34
  # require 'json'
36
35
  #
37
- # # Register custom serializer/deserializer pair
36
+ # # Register custom serializer/deserializer pair, if desired
37
+ # require 'sequel/plugins/serialization'
38
38
  # Sequel::Plugins::Serialization.register_format(:reverse,
39
39
  # lambda{|v| v.reverse},
40
40
  # lambda{|v| v.reverse})
@@ -111,16 +111,7 @@ module Sequel
111
111
  # call be overridden and call super to get the serialization behavior
112
112
  attr_accessor :serialization_module
113
113
 
114
- # Copy the serialization_map and deserialization map into the subclass.
115
- def inherited(subclass)
116
- super
117
- sm = serialization_map.dup
118
- dsm = deserialization_map.dup
119
- subclass.instance_eval do
120
- @deserialization_map = dsm
121
- @serialization_map = sm
122
- end
123
- end
114
+ Plugins.inherited_instance_variables(self, :@deserialization_map=>:dup, :@serialization_map=>:dup)
124
115
 
125
116
  # Create instance level reader that deserializes column values on request,
126
117
  # and instance level writer that stores new deserialized values.
@@ -154,6 +145,8 @@ module Sequel
154
145
  define_method(column) do
155
146
  if deserialized_values.has_key?(column)
156
147
  deserialized_values[column]
148
+ elsif frozen?
149
+ deserialize_value(column, super())
157
150
  else
158
151
  deserialized_values[column] = deserialize_value(column, super())
159
152
  end
@@ -179,6 +172,11 @@ module Sequel
179
172
  @deserialized_values ||= {}
180
173
  end
181
174
 
175
+ def freeze
176
+ deserialized_values.freeze
177
+ super
178
+ end
179
+
182
180
  # Initialization the deserialized values for objects retrieved from the database.
183
181
  def set_values(hash)
184
182
  @deserialized_values.clear if @deserialized_values
@@ -40,10 +40,18 @@ module Sequel
40
40
  # Detect which serialized columns have changed.
41
41
  def changed_columns
42
42
  cc = super
43
+ cc = cc.dup if frozen?
43
44
  deserialized_values.each{|c, v| cc << c if !cc.include?(c) && original_deserialized_value(c) != v}
44
45
  cc
45
46
  end
46
47
 
48
+ # Freeze the original deserialized values when freezing the instance.
49
+ def freeze
50
+ @original_deserialized_values ||= {}
51
+ @original_deserialized_values.freeze
52
+ super
53
+ end
54
+
47
55
  private
48
56
 
49
57
  # For new objects, serialize any existing deserialized values so that changes can
@@ -55,7 +63,11 @@ module Sequel
55
63
 
56
64
  # Return the original deserialized value of the column, caching it to improve performance.
57
65
  def original_deserialized_value(column)
58
- (@original_deserialized_values ||= {})[column] ||= deserialize_value(column, self[column])
66
+ if frozen?
67
+ @original_deserialized_values[column] || deserialize_value(column, self[column])
68
+ else
69
+ (@original_deserialized_values ||= {})[column] ||= deserialize_value(column, self[column])
70
+ end
59
71
  end
60
72
  end
61
73
  end
@@ -77,9 +77,9 @@ module Sequel
77
77
  @sti_model_map = opts[:model_map] || lambda{|v| v if v && v != ''}
78
78
  @sti_key_map = if km = opts[:key_map]
79
79
  if km.is_a?(Hash)
80
- h = Hash.new do |h,k|
80
+ h = Hash.new do |h1,k|
81
81
  unless k.is_a?(String)
82
- h[k.to_s]
82
+ h1[k.to_s]
83
83
  else
84
84
  []
85
85
  end
@@ -93,9 +93,9 @@ module Sequel
93
93
  km
94
94
  end
95
95
  elsif sti_model_map.is_a?(Hash)
96
- h = Hash.new do |h,k|
96
+ h = Hash.new do |h1,k|
97
97
  unless k.is_a?(String)
98
- h[k.to_s]
98
+ h1[k.to_s]
99
99
  else
100
100
  []
101
101
  end
@@ -35,6 +35,15 @@ module Sequel
35
35
  @all.dup
36
36
  end
37
37
 
38
+ # Get the number of records in the cache, without issuing a database query.
39
+ def count(*a, &block)
40
+ if a.empty? && !block
41
+ @all.size
42
+ else
43
+ super
44
+ end
45
+ end
46
+
38
47
  # Return the frozen object with the given pk, or nil if no such object exists
39
48
  # in the cache, without issuing a database query.
40
49
  def cache_get_pk(pk)
@@ -47,33 +56,72 @@ module Sequel
47
56
  @all.each(&block)
48
57
  end
49
58
 
50
- # If no arguments are given, yield each of the model's frozen instances to the block.
51
- # and return a new array, without issuing a database query. If any arguments are
52
- # given, use the default Sequel behavior.
53
- def map(*a)
54
- if a.empty?
55
- @all.map(&(Proc.new if block_given?))
59
+ # Use the cache instead of a query to get the results.
60
+ def map(column=nil, &block)
61
+ if column
62
+ raise(Error, "Cannot provide both column and block to map") if block
63
+ if column.is_a?(Array)
64
+ @all.map{|r| r.values.values_at(*column)}
65
+ else
66
+ @all.map{|r| r[column]}
67
+ end
56
68
  else
57
- super
69
+ @all.map(&(Proc.new if block_given?))
58
70
  end
59
71
  end
60
72
 
61
- # Reload the cache when the dataset changes.
62
- def set_dataset(*)
63
- s = super
64
- load_cache
65
- s
73
+ Plugins.after_set_dataset(self, :load_cache)
74
+
75
+ # Use the cache instead of a query to get the results.
76
+ def to_hash(key_column = nil, value_column = nil)
77
+ return cache.dup if key_column.nil? && value_column.nil?
78
+
79
+ h = {}
80
+ if value_column
81
+ if value_column.is_a?(Array)
82
+ if key_column.is_a?(Array)
83
+ each{|r| h[r.values.values_at(*key_column)] = r.values.values_at(*value_column)}
84
+ else
85
+ each{|r| h[r[key_column]] = r.values.values_at(*value_column)}
86
+ end
87
+ else
88
+ if key_column.is_a?(Array)
89
+ each{|r| h[r.values.values_at(*key_column)] = r[value_column]}
90
+ else
91
+ each{|r| h[r[key_column]] = r[value_column]}
92
+ end
93
+ end
94
+ elsif key_column.is_a?(Array)
95
+ each{|r| h[r.values.values_at(*key_column)] = r}
96
+ else
97
+ each{|r| h[r[key_column]] = r}
98
+ end
99
+ h
66
100
  end
67
101
 
68
- # If no arguments are given, yield an identity map for the model with frozen primary keys
69
- # and instances, without issuing a database query. If any arguments are
70
- # given, use the default Sequel behavior.
71
- def to_hash(*a)
72
- if a.empty?
73
- cache.dup
102
+ # Use the cache instead of a query to get the results
103
+ def to_hash_groups(key_column, value_column = nil)
104
+ h = {}
105
+ if value_column
106
+ if value_column.is_a?(Array)
107
+ if key_column.is_a?(Array)
108
+ each{|r| (h[r.values.values_at(*key_column)] ||= []) << r.values.values_at(*value_column)}
109
+ else
110
+ each{|r| (h[r[key_column]] ||= []) << r.values.values_at(*value_column)}
111
+ end
112
+ else
113
+ if key_column.is_a?(Array)
114
+ each{|r| (h[r.values.values_at(*key_column)] ||= []) << r[value_column]}
115
+ else
116
+ each{|r| (h[r[key_column]] ||= []) << r[value_column]}
117
+ end
118
+ end
119
+ elsif key_column.is_a?(Array)
120
+ each{|r| (h[r.values.values_at(*key_column)] ||= []) << r}
74
121
  else
75
- super
122
+ each{|r| (h[r[key_column]] ||= []) << r}
76
123
  end
124
+ h
77
125
  end
78
126
 
79
127
  private
@@ -22,34 +22,24 @@ module Sequel
22
22
  # # Make the Album class strip strings
23
23
  # Album.plugin :string_stripper
24
24
  module StringStripper
25
- # Set blob columns as skipping stripping when plugin is loaded.
25
+ def self.apply(model)
26
+ model.plugin(:input_transformer, :string_stripper){|v| (v.is_a?(String) && !v.is_a?(SQL::Blob)) ? v.strip : v}
27
+ end
26
28
  def self.configure(model)
27
- model.instance_variable_set(:@skipped_string_stripping_columns, [])
28
- model.send(:set_skipped_string_stripping_columns)
29
+ model.instance_eval{set_skipped_string_stripping_columns if @dataset}
29
30
  end
30
31
 
31
32
  module ClassMethods
32
- # Copy skipped stripping columns from superclass into subclass.
33
- def inherited(subclass)
34
- subclass.instance_variable_set(:@skipped_string_stripping_columns, @skipped_string_stripping_columns.dup)
35
- super
36
- end
37
-
38
- # Set blob columns as skipping stripping when plugin is loaded.
39
- def set_dataset(*)
40
- res = super
41
- set_skipped_string_stripping_columns
42
- res
43
- end
33
+ Plugins.after_set_dataset(self, :set_skipped_string_stripping_columns)
44
34
 
45
35
  # Skip stripping for the given columns.
46
36
  def skip_string_stripping(*columns)
47
- @skipped_string_stripping_columns.concat(columns).uniq!
37
+ skip_input_transformer(:string_stripper, *columns)
48
38
  end
49
39
 
50
40
  # Return true if the column should not have values stripped.
51
41
  def skip_string_stripping?(column)
52
- @skipped_string_stripping_columns.include?(column)
42
+ skip_input_transformer?(:string_stripper, column)
53
43
  end
54
44
 
55
45
  private
@@ -62,16 +52,6 @@ module Sequel
62
52
  end
63
53
  end
64
54
  end
65
-
66
- module InstanceMethods
67
- # Strip value if it is a non-blob string and the model hasn't been set
68
- # to skip stripping for the column, before attempting to assign
69
- # it to the model's values.
70
- def []=(k, v)
71
- v = v.strip if v.is_a?(String) && !v.is_a?(SQL::Blob) && !model.skip_string_stripping?(k)
72
- super(k, v)
73
- end
74
- end
75
55
  end
76
56
  end
77
57
  end