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
@@ -33,12 +33,7 @@ module Sequel
33
33
  end
34
34
 
35
35
  module ClassMethods
36
- # Create boolean readers for the class using the columns from the new dataset.
37
- def set_dataset(*args)
38
- super
39
- create_boolean_readers
40
- self
41
- end
36
+ Plugins.after_set_dataset(self, :create_boolean_readers)
42
37
 
43
38
  private
44
39
 
@@ -70,18 +70,7 @@ module Sequel
70
70
  "#{self}:#{Array(pk).join(',')}"
71
71
  end
72
72
 
73
- # Copy the necessary class instance variables to the subclass.
74
- def inherited(subclass)
75
- super
76
- store = @cache_store
77
- ttl = @cache_ttl
78
- cache_ignore_exceptions = @cache_ignore_exceptions
79
- subclass.instance_eval do
80
- @cache_store = store
81
- @cache_ttl = ttl
82
- @cache_ignore_exceptions = cache_ignore_exceptions
83
- end
84
- end
73
+ Plugins.inherited_instance_variables(self, :@cache_store=>nil, :@cache_ttl=>nil, :@cache_ignore_exceptions=>nil)
85
74
 
86
75
  # Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
87
76
  def set_cache_ttl(ttl)
@@ -92,7 +81,11 @@ module Sequel
92
81
 
93
82
  # Delete the entry with the matching key from the cache
94
83
  def cache_delete(ck)
95
- @cache_store.delete(ck)
84
+ if @cache_ignore_exceptions
85
+ @cache_store.delete(ck) rescue nil
86
+ else
87
+ @cache_store.delete(ck)
88
+ end
96
89
  nil
97
90
  end
98
91
 
@@ -187,6 +187,7 @@ module Sequel
187
187
  # Delete the row from all backing tables, starting from the
188
188
  # most recent table and going through all superclasses.
189
189
  def delete
190
+ raise Sequel::Error, "can't delete frozen object" if frozen?
190
191
  m = model
191
192
  m.cti_tables.reverse.each do |table|
192
193
  m.db.from(table).filter(m.primary_key=>pk).delete
@@ -124,12 +124,7 @@ module Sequel
124
124
  define_composition_accessor(name, opts)
125
125
  end
126
126
 
127
- # Copy the necessary class instance variables to the subclass.
128
- def inherited(subclass)
129
- super
130
- c = compositions.dup
131
- subclass.instance_eval{@compositions = c}
132
- end
127
+ Plugins.inherited_instance_variables(self, :@compositions=>:dup)
133
128
 
134
129
  # Define getter and setter methods for the composition object.
135
130
  def define_composition_accessor(name, opts={})
@@ -137,7 +132,13 @@ module Sequel
137
132
  composer = opts[:composer]
138
133
  composition_module.class_eval do
139
134
  define_method(name) do
140
- compositions.include?(name) ? compositions[name] : (compositions[name] = instance_eval(&composer))
135
+ if compositions.has_key?(name)
136
+ compositions[name]
137
+ elsif frozen?
138
+ instance_eval(&composer)
139
+ else
140
+ compositions[name] = instance_eval(&composer)
141
+ end
141
142
  end
142
143
  define_method("#{name}=") do |v|
143
144
  modified!
@@ -165,6 +166,12 @@ module Sequel
165
166
  def compositions
166
167
  @compositions ||= {}
167
168
  end
169
+
170
+ # Freeze compositions hash when freezing model instance.
171
+ def freeze
172
+ compositions.freeze
173
+ super
174
+ end
168
175
  end
169
176
  end
170
177
  end
@@ -61,19 +61,8 @@ module Sequel
61
61
  # The name of the table containing the constraint validations metadata.
62
62
  attr_reader :constraint_validations_table
63
63
 
64
- # Copy the name of the constraint validations metadata table into the subclass.
65
- def inherited(subclass)
66
- super
67
- subclass.instance_variable_set(:@constraint_validations_table, @constraint_validations_table)
68
- end
69
-
70
- # Parse the constraint validations from the database whenever the dataset
71
- # changes.
72
- def set_dataset(*)
73
- r = super
74
- parse_constraint_validations
75
- r
76
- end
64
+ Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil)
65
+ Plugins.after_set_dataset(self, :parse_constraint_validations)
77
66
 
78
67
  private
79
68
 
@@ -28,12 +28,7 @@ module Sequel
28
28
  # this hash to set specific default values, by default the ones will be parsed from the database.
29
29
  attr_reader :default_values
30
30
 
31
- # Set the default values when loading the dataset.
32
- def set_dataset(*)
33
- x = super
34
- set_default_values
35
- x
36
- end
31
+ Plugins.after_set_dataset(self, :set_default_values)
37
32
 
38
33
  private
39
34
 
@@ -101,6 +101,14 @@ module Sequel
101
101
  @initial_values ||= {}
102
102
  end
103
103
 
104
+ # Freeze internal data structures
105
+ def freeze
106
+ initial_values.freeze
107
+ missing_initial_values.freeze
108
+ @previous_changes.freeze if @previous_changes
109
+ super
110
+ end
111
+
104
112
  # Reset the column to its initial value. If the column was not set
105
113
  # initial, removes it from the values.
106
114
  #
@@ -0,0 +1,54 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The error_splitter plugin automatically splits errors entries related to
4
+ # multiple columns to have separate error entries, one per column. For example,
5
+ # a multiple column uniqueness entry:
6
+ #
7
+ # validates_unique([:artist_id, :name])
8
+ #
9
+ # would by default result in errors entries such as:
10
+ #
11
+ # {[:artist_id, :name]=>'is already taken'}
12
+ #
13
+ # This plugin transforms those errors into:
14
+ #
15
+ # {:artist_id=>'is already taken', :name=>'is already taken'}
16
+ #
17
+ # The main reason to split errors is if you have a list of fields that you
18
+ # are checking for validation errors. If you don't split the errors, then:
19
+ #
20
+ # errors.on(:artist_id)
21
+ #
22
+ # would not return the uniqueness error.
23
+ #
24
+ # Usage:
25
+ #
26
+ # # Make all model subclass instances split errors (called before loading subclasses)
27
+ # Sequel::Model.plugin :error_splitter
28
+ #
29
+ # # Make the Album class split errors
30
+ # Album.plugin :error_splitter
31
+ module ErrorSplitter
32
+ module InstanceMethods
33
+ # If the model instance is not valid, go through all of the errors entries. For
34
+ # any that apply to multiple columns, remove them and add separate error entries,
35
+ # one per column.
36
+ def _valid?(*)
37
+ v = super
38
+ unless v
39
+ errors.keys.select{|k| k.is_a?(Array)}.each do |ks|
40
+ msgs = errors.delete(ks)
41
+ ks.each do |k|
42
+ msgs.each do |msg|
43
+ errors.add(k, msg)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ v
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
@@ -32,11 +32,7 @@ module Sequel
32
32
  # The string encoding to force on a column string values
33
33
  attr_accessor :forced_encoding
34
34
 
35
- # Copy the forced_encoding value into the subclass
36
- def inherited(subclass)
37
- super
38
- subclass.forced_encoding = forced_encoding
39
- end
35
+ Plugins.inherited_instance_variables(self, :@forced_encoding=>nil)
40
36
  end
41
37
 
42
38
  module InstanceMethods
@@ -91,12 +91,7 @@ module Sequel
91
91
  @hooks[hook].each{|k,v| yield v}
92
92
  end
93
93
 
94
- # Make a copy of the current class's hooks for the subclass.
95
- def inherited(subclass)
96
- hooks = subclass.instance_variable_set(:@hooks, {})
97
- instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
98
- super
99
- end
94
+ Plugins.inherited_instance_variables(self, :@hooks=>:hash_dup)
100
95
 
101
96
  private
102
97
 
@@ -0,0 +1,79 @@
1
+ module Sequel
2
+ module Plugins
3
+ # InputTransformer is a plugin that allows generic transformations
4
+ # of input values in model column setters. Example:
5
+ #
6
+ # Album.plugin :input_transformer
7
+ # Album.add_input_transformer(:reverser){|v| v.is_a?(String) ? v.reverse : v}
8
+ # album = Album.new(:name=>'foo')
9
+ # album.name # => 'oof'
10
+ #
11
+ # You can specifically set some columns to skip some input
12
+ # input transformers:
13
+ #
14
+ # Album.skip_input_transformer(:reverser, :foo)
15
+ # Album.new(:foo=>'bar').foo # => 'bar'
16
+ #
17
+ # Usage:
18
+ #
19
+ # # Make all model subclass instances support input transformers (called before loading subclasses)
20
+ # Sequel::Model.plugin :input_transformer
21
+ #
22
+ # # Make the Album class support input transformers
23
+ # Album.plugin :input_transformer
24
+ module InputTransformer
25
+ def self.apply(model, *)
26
+ model.instance_eval do
27
+ @input_transformers = {}
28
+ @input_transformer_order = []
29
+ @skip_input_transformer_columns = {}
30
+ end
31
+ end
32
+
33
+ # If an input transformer is given in the plugin call,
34
+ # add it as a transformer
35
+ def self.configure(model, transformer_name=nil, &block)
36
+ model.add_input_transformer(transformer_name, &block) if transformer_name || block
37
+ end
38
+
39
+ module ClassMethods
40
+ # Hash of input transformer name symbols to transformer callables.
41
+ attr_reader :input_transformers
42
+
43
+ # The order in which to call the input transformers.
44
+ attr_reader :input_transformer_order
45
+
46
+ Plugins.inherited_instance_variables(self, :@skip_input_transformer_columns=>:hash_dup, :@input_transformers=>:dup, :@input_transformer_order=>:dup)
47
+
48
+ # Add an input transformer to this model.
49
+ def add_input_transformer(transformer_name, &block)
50
+ raise(Error, 'must provide both transformer name and block when adding input transformer') unless transformer_name && block
51
+ @input_transformers[transformer_name] = block
52
+ @input_transformer_order.unshift(transformer_name)
53
+ @skip_input_transformer_columns[transformer_name] = []
54
+ end
55
+
56
+ # Set columns that the transformer should skip.
57
+ def skip_input_transformer(transformer_name, *columns)
58
+ @skip_input_transformer_columns[transformer_name].concat(columns).uniq!
59
+ end
60
+
61
+ # Return true if the transformer should not be called for the given column.
62
+ def skip_input_transformer?(transformer_name, column)
63
+ @skip_input_transformer_columns[transformer_name].include?(column)
64
+ end
65
+ end
66
+
67
+ module InstanceMethods
68
+ # Transform the input using all of the transformers, except those explicitly
69
+ # skipped, before setting the value in the model object.
70
+ def []=(k, v)
71
+ model.input_transformer_order.each do |transformer_name|
72
+ v = model.input_transformers[transformer_name].call(v) unless model.skip_input_transformer?(transformer_name, k)
73
+ end
74
+ super
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -58,6 +58,12 @@ module Sequel
58
58
  super
59
59
  clear_instance_filters
60
60
  end
61
+
62
+ # Freeze the instance filters when freezing the object
63
+ def freeze
64
+ instance_filters.freeze
65
+ super
66
+ end
61
67
 
62
68
  # Add an instance filter to the array of instance filters
63
69
  # Both the arguments given and the block are passed to the
@@ -85,7 +91,7 @@ module Sequel
85
91
 
86
92
  # Apply the instance filters to the given dataset
87
93
  def apply_instance_filters(ds)
88
- instance_filters.inject(ds){|ds, i| ds.filter(*i[0], &i[1])}
94
+ instance_filters.inject(ds){|ds1, i| ds1.filter(*i[0], &i[1])}
89
95
  end
90
96
 
91
97
  # Clear the instance filters.
@@ -29,7 +29,13 @@ module Sequel
29
29
  BEFORE_HOOKS = Sequel::Model::BEFORE_HOOKS
30
30
  AFTER_HOOKS = Sequel::Model::AFTER_HOOKS - [:after_initialize]
31
31
  HOOKS = BEFORE_HOOKS + AFTER_HOOKS
32
- HOOKS.each{|h| class_eval("def #{h}_hook(&block); add_instance_hook(:#{h}, &block); self end", __FILE__, __LINE__)}
32
+ HOOKS.each{|h| class_eval(<<-END , __FILE__, __LINE__+1)}
33
+ def #{h}_hook(&block)
34
+ raise Sequel::Error, "can't add hooks to frozen object" if frozen?
35
+ add_instance_hook(:#{h}, &block)
36
+ self
37
+ end
38
+ END
33
39
 
34
40
  BEFORE_HOOKS.each{|h| class_eval("def #{h}; run_before_instance_hooks(:#{h}) == false ? false : super end", __FILE__, __LINE__)}
35
41
  AFTER_HOOKS.each{|h| class_eval(<<-END, __FILE__, __LINE__ + 1)}
@@ -175,18 +175,13 @@ module Sequel
175
175
  new.from_json_node(hash, {:all_columns=>true, :all_associations=>true}.merge(opts))
176
176
  end
177
177
 
178
- # Call the dataset +to_json+ method.
179
- def to_json(*a)
180
- dataset.to_json(*a)
181
- end
182
-
183
- # Copy the current model object's default json options into the subclass.
184
- def inherited(subclass)
185
- super
178
+ Plugins.inherited_instance_variables(self, :@json_serializer_opts=>lambda do |json_serializer_opts|
186
179
  opts = {}
187
180
  json_serializer_opts.each{|k, v| opts[k] = (v.is_a?(Array) || v.is_a?(Hash)) ? v.dup : v}
188
- subclass.instance_variable_set(:@json_serializer_opts, opts)
189
- end
181
+ opts
182
+ end)
183
+
184
+ Plugins.def_dataset_methods(self, :to_json)
190
185
  end
191
186
 
192
187
  module InstanceMethods
@@ -5,7 +5,7 @@ module Sequel
5
5
  # is needed after the instance has been retrieved, a database query is made to
6
6
  # retreive the value of the attribute.
7
7
  #
8
- # This plugin depends on the identity_map and tactical_eager_loading plugin, and allows you to
8
+ # This plugin depends on the tactical_eager_loading plugin, and allows you to
9
9
  # eagerly load lazy attributes for all objects retrieved with the current object.
10
10
  # So the following code should issue one query to get the albums and one query to
11
11
  # get the reviews for all of those albums:
@@ -20,9 +20,8 @@ module Sequel
20
20
  # # You can specify multiple columns to lazily load:
21
21
  # Album.plugin :lazy_attributes, :review, :tracklist
22
22
  module LazyAttributes
23
- # Lazy attributes requires the identity map and tactical eager loading plugins
23
+ # Lazy attributes requires the tactical_eager_loading plugin
24
24
  def self.apply(model, *attrs)
25
- model.plugin :identity_map
26
25
  model.plugin :tactical_eager_loading
27
26
  end
28
27
 
@@ -51,7 +50,7 @@ module Sequel
51
50
  include(self.lazy_attributes_module ||= Module.new) unless lazy_attributes_module
52
51
  lazy_attributes_module.class_eval do
53
52
  define_method(a) do
54
- if !values.include?(a) && !new?
53
+ if !values.has_key?(a) && !new?
55
54
  lazy_attribute_lookup(a)
56
55
  else
57
56
  super()
@@ -69,9 +68,23 @@ module Sequel
69
68
  # the attribute for just the current object. Return the value of
70
69
  # the attribute for the current object.
71
70
  def lazy_attribute_lookup(a)
72
- primary_key = model.primary_key
73
- model.select(*(Array(primary_key) + [a])).filter(primary_key=>retrieved_with.map{|o| o.pk}).all if model.identity_map && retrieved_with
74
- values[a] = this.select(a).first[a] unless values.include?(a)
71
+ if frozen?
72
+ return this.dup.select(a).get(a)
73
+ end
74
+
75
+ if retrieved_with
76
+ raise(Error, "Invalid primary key column for #{model}: #{pkc.inspect}") unless primary_key = model.primary_key
77
+ composite_pk = true if primary_key.is_a?(Array)
78
+ id_map = {}
79
+ retrieved_with.each{|o| id_map[o.pk] = o unless o.values.has_key?(a) || o.frozen?}
80
+ model.select(*(Array(primary_key) + [a])).filter(primary_key=>id_map.keys).naked.each do |row|
81
+ obj = id_map[composite_pk ? row.values_at(*primary_key) : row[primary_key]]
82
+ if obj && !obj.values.has_key?(a)
83
+ obj.values[a] = row[a]
84
+ end
85
+ end
86
+ end
87
+ values[a] = this.select(a).get(a) unless values.has_key?(a)
75
88
  values[a]
76
89
  end
77
90
  end
@@ -78,12 +78,7 @@ module Sequel
78
78
  # proc should accept an instance and return a dataset representing the list.
79
79
  attr_accessor :scope_proc
80
80
 
81
- # Copy the +position_field+ and +scope_proc+ to the subclass.
82
- def inherited(subclass)
83
- super
84
- subclass.position_field = position_field
85
- subclass.scope_proc = scope_proc
86
- end
81
+ Plugins.inherited_instance_variables(self, :@position_field=>nil, :@scope_proc=>nil)
87
82
  end
88
83
 
89
84
  module InstanceMethods