sequel 3.46.0 → 3.47.0

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