sequel 3.21.0 → 3.32.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 (234) hide show
  1. data/CHANGELOG +539 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -6
  4. data/Rakefile +37 -19
  5. data/bin/sequel +1 -5
  6. data/doc/advanced_associations.rdoc +17 -0
  7. data/doc/association_basics.rdoc +293 -43
  8. data/doc/dataset_basics.rdoc +4 -4
  9. data/doc/dataset_filtering.rdoc +8 -0
  10. data/doc/mass_assignment.rdoc +54 -0
  11. data/doc/migration.rdoc +15 -538
  12. data/doc/model_hooks.rdoc +72 -27
  13. data/doc/opening_databases.rdoc +89 -50
  14. data/doc/prepared_statements.rdoc +40 -13
  15. data/doc/reflection.rdoc +8 -2
  16. data/doc/release_notes/3.22.0.txt +39 -0
  17. data/doc/release_notes/3.23.0.txt +172 -0
  18. data/doc/release_notes/3.24.0.txt +420 -0
  19. data/doc/release_notes/3.25.0.txt +88 -0
  20. data/doc/release_notes/3.26.0.txt +88 -0
  21. data/doc/release_notes/3.27.0.txt +82 -0
  22. data/doc/release_notes/3.28.0.txt +304 -0
  23. data/doc/release_notes/3.29.0.txt +459 -0
  24. data/doc/release_notes/3.30.0.txt +135 -0
  25. data/doc/release_notes/3.31.0.txt +146 -0
  26. data/doc/release_notes/3.32.0.txt +202 -0
  27. data/doc/schema_modification.rdoc +547 -0
  28. data/doc/sharding.rdoc +7 -1
  29. data/doc/testing.rdoc +129 -0
  30. data/doc/transactions.rdoc +137 -0
  31. data/doc/virtual_rows.rdoc +2 -2
  32. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  33. data/lib/sequel/adapters/ado.rb +40 -18
  34. data/lib/sequel/adapters/amalgalite.rb +17 -9
  35. data/lib/sequel/adapters/db2.rb +175 -86
  36. data/lib/sequel/adapters/dbi.rb +15 -15
  37. data/lib/sequel/adapters/do/mysql.rb +5 -7
  38. data/lib/sequel/adapters/do/postgres.rb +0 -5
  39. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  40. data/lib/sequel/adapters/do.rb +17 -36
  41. data/lib/sequel/adapters/firebird.rb +27 -208
  42. data/lib/sequel/adapters/ibmdb.rb +448 -0
  43. data/lib/sequel/adapters/informix.rb +6 -23
  44. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  45. data/lib/sequel/adapters/jdbc/db2.rb +52 -0
  46. data/lib/sequel/adapters/jdbc/derby.rb +260 -0
  47. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  48. data/lib/sequel/adapters/jdbc/h2.rb +66 -30
  49. data/lib/sequel/adapters/jdbc/hsqldb.rb +177 -0
  50. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  51. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  52. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  53. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  54. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  58. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  59. data/lib/sequel/adapters/jdbc.rb +231 -78
  60. data/lib/sequel/adapters/mock.rb +339 -0
  61. data/lib/sequel/adapters/mysql.rb +85 -184
  62. data/lib/sequel/adapters/mysql2.rb +26 -25
  63. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  64. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  65. data/lib/sequel/adapters/odbc.rb +28 -9
  66. data/lib/sequel/adapters/openbase.rb +3 -5
  67. data/lib/sequel/adapters/oracle.rb +355 -53
  68. data/lib/sequel/adapters/postgres.rb +194 -33
  69. data/lib/sequel/adapters/shared/access.rb +32 -9
  70. data/lib/sequel/adapters/shared/db2.rb +328 -0
  71. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  72. data/lib/sequel/adapters/shared/informix.rb +53 -0
  73. data/lib/sequel/adapters/shared/mssql.rb +278 -107
  74. data/lib/sequel/adapters/shared/mysql.rb +203 -65
  75. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  76. data/lib/sequel/adapters/shared/oracle.rb +237 -46
  77. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  78. data/lib/sequel/adapters/shared/progress.rb +5 -8
  79. data/lib/sequel/adapters/shared/sqlite.rb +200 -50
  80. data/lib/sequel/adapters/sqlite.rb +29 -21
  81. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  82. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  83. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  84. data/lib/sequel/adapters/swift.rb +5 -5
  85. data/lib/sequel/adapters/tinytds.rb +160 -19
  86. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  87. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  88. data/lib/sequel/ast_transformer.rb +190 -0
  89. data/lib/sequel/connection_pool/threaded.rb +3 -2
  90. data/lib/sequel/connection_pool.rb +1 -1
  91. data/lib/sequel/core.rb +68 -5
  92. data/lib/sequel/database/connecting.rb +5 -6
  93. data/lib/sequel/database/dataset.rb +4 -4
  94. data/lib/sequel/database/dataset_defaults.rb +59 -1
  95. data/lib/sequel/database/logging.rb +1 -1
  96. data/lib/sequel/database/misc.rb +104 -19
  97. data/lib/sequel/database/query.rb +163 -59
  98. data/lib/sequel/database/schema_generator.rb +17 -7
  99. data/lib/sequel/database/schema_methods.rb +59 -29
  100. data/lib/sequel/dataset/actions.rb +240 -76
  101. data/lib/sequel/dataset/features.rb +91 -8
  102. data/lib/sequel/dataset/graph.rb +38 -11
  103. data/lib/sequel/dataset/misc.rb +50 -23
  104. data/lib/sequel/dataset/mutation.rb +12 -7
  105. data/lib/sequel/dataset/prepared_statements.rb +59 -27
  106. data/lib/sequel/dataset/query.rb +236 -43
  107. data/lib/sequel/dataset/sql.rb +676 -267
  108. data/lib/sequel/dataset.rb +8 -0
  109. data/lib/sequel/exceptions.rb +4 -0
  110. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  111. data/lib/sequel/extensions/migration.rb +6 -4
  112. data/lib/sequel/extensions/named_timezones.rb +5 -0
  113. data/lib/sequel/extensions/query.rb +1 -1
  114. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  115. data/lib/sequel/extensions/to_dot.rb +95 -83
  116. data/lib/sequel/model/associations.rb +1081 -344
  117. data/lib/sequel/model/base.rb +420 -133
  118. data/lib/sequel/model/errors.rb +1 -1
  119. data/lib/sequel/model/exceptions.rb +5 -1
  120. data/lib/sequel/model.rb +20 -10
  121. data/lib/sequel/plugins/active_model.rb +13 -1
  122. data/lib/sequel/plugins/association_pks.rb +22 -4
  123. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  124. data/lib/sequel/plugins/composition.rb +43 -10
  125. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  126. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  127. data/lib/sequel/plugins/force_encoding.rb +6 -6
  128. data/lib/sequel/plugins/identity_map.rb +114 -7
  129. data/lib/sequel/plugins/many_through_many.rb +68 -7
  130. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  131. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  132. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  133. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  134. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  135. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  136. data/lib/sequel/plugins/serialization.rb +98 -49
  137. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  138. data/lib/sequel/plugins/sharding.rb +21 -54
  139. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  140. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  141. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  142. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  143. data/lib/sequel/sql.rb +109 -52
  144. data/lib/sequel/timezones.rb +57 -38
  145. data/lib/sequel/version.rb +1 -1
  146. data/spec/adapters/db2_spec.rb +146 -0
  147. data/spec/adapters/mssql_spec.rb +135 -30
  148. data/spec/adapters/mysql_spec.rb +244 -212
  149. data/spec/adapters/oracle_spec.rb +67 -77
  150. data/spec/adapters/postgres_spec.rb +267 -78
  151. data/spec/adapters/spec_helper.rb +6 -5
  152. data/spec/adapters/sqlite_spec.rb +35 -21
  153. data/spec/core/connection_pool_spec.rb +71 -92
  154. data/spec/core/core_sql_spec.rb +21 -30
  155. data/spec/core/database_spec.rb +789 -512
  156. data/spec/core/dataset_spec.rb +1176 -1061
  157. data/spec/core/expression_filters_spec.rb +159 -42
  158. data/spec/core/mock_adapter_spec.rb +419 -0
  159. data/spec/core/object_graph_spec.rb +86 -114
  160. data/spec/core/schema_generator_spec.rb +3 -3
  161. data/spec/core/schema_spec.rb +298 -38
  162. data/spec/core/spec_helper.rb +6 -48
  163. data/spec/extensions/active_model_spec.rb +13 -0
  164. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  165. data/spec/extensions/association_dependencies_spec.rb +2 -12
  166. data/spec/extensions/association_pks_spec.rb +66 -32
  167. data/spec/extensions/caching_spec.rb +23 -50
  168. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  169. data/spec/extensions/columns_introspection_spec.rb +91 -0
  170. data/spec/extensions/composition_spec.rb +18 -13
  171. data/spec/extensions/dataset_associations_spec.rb +199 -0
  172. data/spec/extensions/defaults_setter_spec.rb +64 -0
  173. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  174. data/spec/extensions/identity_map_spec.rb +114 -22
  175. data/spec/extensions/instance_filters_spec.rb +11 -21
  176. data/spec/extensions/instance_hooks_spec.rb +53 -0
  177. data/spec/extensions/json_serializer_spec.rb +4 -5
  178. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  179. data/spec/extensions/list_spec.rb +17 -39
  180. data/spec/extensions/many_through_many_spec.rb +283 -250
  181. data/spec/extensions/migration_spec.rb +35 -32
  182. data/spec/extensions/named_timezones_spec.rb +23 -3
  183. data/spec/extensions/nested_attributes_spec.rb +97 -88
  184. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  185. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  186. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  187. data/spec/extensions/prepared_statements_spec.rb +53 -0
  188. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  189. data/spec/extensions/pretty_table_spec.rb +1 -6
  190. data/spec/extensions/query_spec.rb +6 -0
  191. data/spec/extensions/rcte_tree_spec.rb +41 -43
  192. data/spec/extensions/schema_dumper_spec.rb +5 -8
  193. data/spec/extensions/schema_spec.rb +13 -21
  194. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  195. data/spec/extensions/serialization_spec.rb +81 -43
  196. data/spec/extensions/sharding_spec.rb +66 -140
  197. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  198. data/spec/extensions/spec_helper.rb +13 -61
  199. data/spec/extensions/sql_expr_spec.rb +20 -60
  200. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  201. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  202. data/spec/extensions/timestamps_spec.rb +6 -6
  203. data/spec/extensions/to_dot_spec.rb +4 -7
  204. data/spec/extensions/touch_spec.rb +13 -14
  205. data/spec/extensions/tree_spec.rb +11 -26
  206. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  207. data/spec/extensions/update_primary_key_spec.rb +30 -24
  208. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  209. data/spec/extensions/validation_helpers_spec.rb +16 -35
  210. data/spec/extensions/xml_serializer_spec.rb +16 -4
  211. data/spec/integration/associations_test.rb +661 -21
  212. data/spec/integration/database_test.rb +4 -2
  213. data/spec/integration/dataset_test.rb +500 -67
  214. data/spec/integration/eager_loader_test.rb +19 -21
  215. data/spec/integration/model_test.rb +80 -1
  216. data/spec/integration/plugin_test.rb +367 -119
  217. data/spec/integration/prepared_statement_test.rb +222 -125
  218. data/spec/integration/schema_test.rb +217 -39
  219. data/spec/integration/spec_helper.rb +39 -30
  220. data/spec/integration/timezone_test.rb +38 -12
  221. data/spec/integration/transaction_test.rb +182 -5
  222. data/spec/integration/type_test.rb +17 -3
  223. data/spec/model/association_reflection_spec.rb +201 -7
  224. data/spec/model/associations_spec.rb +955 -662
  225. data/spec/model/base_spec.rb +275 -95
  226. data/spec/model/dataset_methods_spec.rb +7 -27
  227. data/spec/model/eager_loading_spec.rb +677 -676
  228. data/spec/model/hooks_spec.rb +349 -67
  229. data/spec/model/model_spec.rb +222 -168
  230. data/spec/model/plugins_spec.rb +24 -13
  231. data/spec/model/record_spec.rb +381 -218
  232. data/spec/model/spec_helper.rb +13 -71
  233. data/spec/model/validations_spec.rb +11 -0
  234. metadata +117 -73
@@ -40,7 +40,7 @@ module Sequel
40
40
  def full_messages
41
41
  inject([]) do |m, kv|
42
42
  att, errors = *kv
43
- errors.each {|e| m << "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}"}
43
+ errors.each {|e| m << (e.is_a?(LiteralString) ? e : "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}")}
44
44
  m
45
45
  end
46
46
  end
@@ -1,6 +1,10 @@
1
1
  module Sequel
2
2
  # Exception class raised when +raise_on_save_failure+ is set and a before hook returns false
3
- class BeforeHookFailed < Error; end
3
+ # or an around hook doesn't call super or yield.
4
+ class HookFailed < Error; end
5
+
6
+ # Deprecated alias for HookFailed, kept for backwards compatibility
7
+ BeforeHookFailed = HookFailed
4
8
 
5
9
  # Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
6
10
  # modify a single row.
data/lib/sequel/model.rb CHANGED
@@ -11,9 +11,9 @@ module Sequel
11
11
  # with the +Database+ in +source+ to create the
12
12
  # dataset to use)
13
13
  # Dataset :: Sets the dataset for this model to +source+.
14
- # Symbol :: Sets the table name for this model to +source+. The
15
- # class will use the default database for model
16
- # classes in order to create the dataset.
14
+ # other :: Sets the table name for this model to +source+. The
15
+ # class will use the default database for model
16
+ # classes in order to create the dataset.
17
17
  #
18
18
  # The purpose of this method is to set the dataset/database automatically
19
19
  # for a model class, if the table name doesn't match the implicit
@@ -49,10 +49,11 @@ module Sequel
49
49
  # called directly on the class. Model datasets return rows as model instances,
50
50
  # which have fairly standard ORM instance behavior.
51
51
  #
52
- # <tt>Sequel::Model</tt> is built completely out of plugins, the only method not part of a
53
- # plugin is the plugin method itself. Plugins can override any class, instance, or
54
- # dataset method defined by a previous plugin and call super to get the default
55
- # behavior.
52
+ # <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
53
+ # instance, or dataset method defined by a previous plugin and call super to get the default
54
+ # behavior. By default, <tt>Sequel::Model</tt> loads two plugins, <tt>Sequel::Model</tt>
55
+ # (which is itself a plugin) for the base support, and <tt>Sequel::Model::Associations</tt>
56
+ # for the associations support.
56
57
  #
57
58
  # You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
58
59
  # make Sequel not load the associations plugin by default.
@@ -69,7 +70,8 @@ module Sequel
69
70
  EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
70
71
 
71
72
  # Boolean settings that can be modified at the global, class, or instance level.
72
- BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions]
73
+ BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
74
+ :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_after_commit_rollback, :use_transactions]
73
75
 
74
76
  # Hooks that are called before an action. Can return false to not do the action. When
75
77
  # overriding these, it is recommended to call +super+ as the last line of your method,
@@ -78,7 +80,13 @@ module Sequel
78
80
 
79
81
  # Hooks that are called after an action. When overriding these, it is recommended to call
80
82
  # +super+ on the first line of your method, so later hooks are called after earlier hooks.
81
- AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy, :after_validation]
83
+ AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy,
84
+ :after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
85
+
86
+ # Hooks that are called around an action. If overridden, these methods must call super
87
+ # exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
88
+ # raised by the code they wrap or ensure that some behavior is executed no matter what.
89
+ AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
82
90
 
83
91
  # Empty instance methods to create that the user can override to get hook/callback behavior.
84
92
  # Just like any other method defined by Sequel, if you override one of these, you should
@@ -96,7 +104,8 @@ module Sequel
96
104
  :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
97
105
  :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
98
106
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
99
- :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil}
107
+ :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
108
+ :@use_after_commit_rollback=>nil}
100
109
 
101
110
  # Regular expression that determines if a method name is normal in the sense that
102
111
  # it could be used literally in ruby code without using send. Used to
@@ -126,6 +135,7 @@ module Sequel
126
135
  @strict_param_setting = true
127
136
  @typecast_empty_string_to_nil = true
128
137
  @typecast_on_assignment = true
138
+ @use_after_commit_rollback = true
129
139
  @use_transactions = true
130
140
 
131
141
  Sequel.require %w"default_inflections inflections plugins base exceptions errors", "model"
@@ -16,7 +16,14 @@ module Sequel
16
16
  # # Make the Album class active_model compliant
17
17
  # Album.plugin :active_model
18
18
  module ActiveModel
19
- ClassMethods = ::ActiveModel::Naming
19
+ module ClassMethods
20
+ include ::ActiveModel::Naming
21
+
22
+ # Class level cache for to_partial_path.
23
+ def _to_partial_path
24
+ @_to_partial_path ||= "#{underscore(pluralize(to_s))}/#{underscore(demodulize(to_s))}".freeze
25
+ end
26
+ end
20
27
 
21
28
  module InstanceMethods
22
29
  # The default string to join composite primary keys with in to_param.
@@ -56,6 +63,11 @@ module Sequel
56
63
  k.join(to_param_joiner)
57
64
  end
58
65
  end
66
+
67
+ # Returns a string identifying the path associated with the object.
68
+ def to_partial_path
69
+ model._to_partial_path
70
+ end
59
71
 
60
72
  private
61
73
 
@@ -14,7 +14,7 @@ module Sequel
14
14
  #
15
15
  # Note that it uses the singular form of the association name. Also note
16
16
  # that the setter both associates to new primary keys not in the assocation
17
- # and disassociated from primary keys not provided to the method.
17
+ # and disassociates from primary keys not provided to the method.
18
18
  #
19
19
  # This plugin makes modifications directly to the underlying tables,
20
20
  # it does not create or return any model objects, and therefore does
@@ -56,6 +56,7 @@ module Sequel
56
56
  _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key])).select_map(opts[:right_key])
57
57
  end
58
58
  def_association_pks_setter(opts) do |pks|
59
+ pks = convert_pk_array(opts, pks)
59
60
  checked_transaction do
60
61
  ds = _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key]))
61
62
  ds.exclude(opts[:right_key]=>pks).delete
@@ -71,17 +72,34 @@ module Sequel
71
72
  super
72
73
  return if opts[:type] == :one_to_one
73
74
  def_association_pks_getter(opts) do
74
- send(opts.dataset_method).select_map(opts[:primary_key])
75
+ send(opts.dataset_method).select_map(opts.associated_class.primary_key)
75
76
  end
76
77
  def_association_pks_setter(opts) do |pks|
78
+ pks = convert_pk_array(opts, pks)
77
79
  checked_transaction do
78
80
  ds = send(opts.dataset_method)
79
- ds.unfiltered.filter(opts[:primary_key]=>pks).update(opts[:key]=>pk)
80
- ds.exclude(opts[:primary_key]=>pks).update(opts[:key]=>nil)
81
+ primary_key = opts.associated_class.primary_key
82
+ key = opts[:key]
83
+ ds.unfiltered.filter(primary_key=>pks).update(key=>pk)
84
+ ds.exclude(primary_key=>pks).update(key=>nil)
81
85
  end
82
86
  end
83
87
  end
84
88
  end
89
+
90
+ module InstanceMethods
91
+ private
92
+
93
+ # If the associated class's primary key column type is integer,
94
+ # typecast all provided values to integer before using them.
95
+ def convert_pk_array(opts, pks)
96
+ if klass = opts.associated_class and sch = klass.db_schema and col = sch[klass.primary_key] and col[:type] == :integer
97
+ pks.map{|pk| model.db.typecast_value(:integer, pk)}
98
+ else
99
+ pks
100
+ end
101
+ end
102
+ end
85
103
  end
86
104
  end
87
105
  end
@@ -90,9 +90,9 @@ module Sequel
90
90
  @cti_columns = {table_name=>columns}
91
91
  @cti_table_map = opts[:table_map] || {}
92
92
  dataset.row_proc = if key
93
- lambda{|r| (m.call(r[key]) rescue model).load(r)}
93
+ lambda{|r| (m.call(r[key]) rescue model).call(r)}
94
94
  else
95
- lambda{|r| model.load(r)}
95
+ model
96
96
  end
97
97
  end
98
98
  end
@@ -152,9 +152,9 @@ module Sequel
152
152
  subclass.instance_eval do
153
153
  m = method(:constantize)
154
154
  dataset.row_proc = if cti_key
155
- lambda{|r| (m.call(r[ck]) rescue subclass).load(r)}
155
+ lambda{|r| (m.call(r[ck]) rescue subclass).call(r)}
156
156
  else
157
- lambda{|r| subclass.load(r)}
157
+ subclass
158
158
  end
159
159
  (columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
160
160
  cti_tables.reverse.each do |table|
@@ -1,20 +1,53 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The composition plugin allows you to easily define getter and
4
- # setter instance methods for a class where the backing data
5
- # is composed of other getters and decomposed to other setters.
3
+ # The composition plugin allows you to easily define a virtual
4
+ # attribute where the backing data is composed of other columns.
6
5
  #
7
- # A simple example of this is when you have a database table with
8
- # separate columns for year, month, and day, but where you want
9
- # to deal with Date objects in your ruby code. This can be handled
10
- # with:
6
+ # There are two ways to use the plugin. One way is with the
7
+ # :mapping option. A simple example of this is when you have a
8
+ # database table with separate columns for year, month, and day,
9
+ # but where you want to deal with Date objects in your ruby code.
10
+ # This can be handled with:
11
11
  #
12
12
  # Album.plugin :composition
13
13
  # Album.composition :date, :mapping=>[:year, :month, :day]
14
14
  #
15
- # The :mapping option is optional, but you can define custom
16
- # composition and decomposition procs via the :composer and
17
- # :decomposer options.
15
+ # With the :mapping option, you can provide a :class option
16
+ # that gives the class to use, but if that is not provided, it
17
+ # is inferred from the name of the composition (e.g. :date -> Date).
18
+ # When the <tt>date</tt> method is called, it will return a
19
+ # Date object by calling:
20
+ #
21
+ # Date.new(year, month, day)
22
+ #
23
+ # When saving the object, if the date composition has been used
24
+ # (by calling either the getter or setter method), it will
25
+ # populate the related columns of the object before saving:
26
+ #
27
+ # self.year = date.year
28
+ # self.month = date.month
29
+ # self.day = date.day
30
+ #
31
+ # The :mapping option is just a shortcut that works in particular
32
+ # cases. To handle any case, you can define a custom :composer
33
+ # and :decomposer procs. The :composer proc will be instance_evaled
34
+ # the first time the getter is called, and the :decomposer proc
35
+ # will be instance_evaled before saving. The above example could
36
+ # also be implemented as:
37
+ #
38
+ # Album.composition, :date,
39
+ # :composer=>proc{Date.new(year, month, day) if year || month || day},
40
+ # :decomposer=>(proc do
41
+ # if d = compositions[:date]
42
+ # self.year = d.year
43
+ # self.month = d.month
44
+ # self.day = d.day
45
+ # else
46
+ # self.year = nil
47
+ # self.month = nil
48
+ # self.day = nil
49
+ # end
50
+ # end)
18
51
  #
19
52
  # Note that when using the composition object, you should not
20
53
  # modify the underlying columns if you are also instantiating
@@ -0,0 +1,100 @@
1
+ module Sequel
2
+ module Plugins
3
+ # DatasetAssociations allows you to easily use your model associations
4
+ # via datasets. For each association you define, it creates a dataset
5
+ # method for that association that returns a dataset of all objects
6
+ # that are associated to objects in the current dataset. Here's a simple
7
+ # example:
8
+ #
9
+ # class Artist < Sequel::Model
10
+ # plugin :dataset_associations
11
+ # one_to_many :albums
12
+ # end
13
+ # Artist.filter(id=>1..100).albums
14
+ # # SELECT * FROM albums
15
+ # # WHERE (albums.artist_id IN (
16
+ # # SELECT id FROM artists
17
+ # # WHERE ((id >= 1) AND (id <= 100))))
18
+ #
19
+ # This works for all of the association types that ship with Sequel,
20
+ # including the many_through_many type. Most association options that
21
+ # are supported when eager loading are supported when using a
22
+ # dataset association. However, associations that use :limit or
23
+ # one_to_one associations that are really one_to_many relationships
24
+ # in the database will not work correctly, returning all associated
25
+ # objects.
26
+ #
27
+ # As the dataset methods return datasets, you can easily chain the
28
+ # methods to get associated datasets of associated datasets:
29
+ #
30
+ # Artist.filter(id=>1..100).albums.filter{name < 'M'}.tags
31
+ # # SELECT tags.* FROM tags
32
+ # # WHERE (tags.id IN (
33
+ # # SELECT albums_tags.tag_id FROM albums
34
+ # # INNER JOIN albums_tags
35
+ # # ON (albums_tags.album_id = albums.id)
36
+ # # WHERE
37
+ # # ((albums.artist_id IN (
38
+ # # SELECT id FROM artists
39
+ # # WHERE ((id >= 1) AND (id <= 100)))
40
+ # # AND
41
+ # # (name < 'M')))))
42
+ #
43
+ # Usage:
44
+ #
45
+ # # Make all model subclasses create association methods for datasets
46
+ # Sequel::Model.plugin :dataset_associations
47
+ #
48
+ # # Make the Album class create association methods for datasets
49
+ # Album.plugin :dataset_associations
50
+ module DatasetAssociations
51
+ module ClassMethods
52
+ # Set up a dataset method for each association to return an associated dataset
53
+ def associate(type, name, *)
54
+ ret = super
55
+ r = association_reflection(name)
56
+ meth = r.returns_array? ? name : pluralize(name).to_sym
57
+ def_dataset_method(meth){associated(name)}
58
+ ret
59
+ end
60
+ end
61
+
62
+ module DatasetMethods
63
+ # For the association given by +name+, return a dataset of associated objects
64
+ # such that it would return the union of calling the association method on
65
+ # all objects returned by the current dataset.
66
+ #
67
+ # This supports most options that are supported when eager loading. It doesn't
68
+ # support limits on the associations, or one_to_one associations that are really
69
+ # one_to_many and use an order to select the first matching object. In both of
70
+ # those cases, this will return an array of all matching objects.
71
+ def associated(name)
72
+ raise Error, "unrecognized association name: #{name.inspect}" unless r = model.association_reflection(name)
73
+ ds = r.associated_class.dataset
74
+ sds = opts[:limit] ? self : unordered
75
+ ds = case r[:type]
76
+ when :many_to_one
77
+ ds.filter(r.qualified_primary_key=>sds.select(*Array(r[:qualified_key])))
78
+ when :one_to_one, :one_to_many
79
+ ds.filter(r.qualified_key=>sds.select(*Array(r.qualified_primary_key)))
80
+ when :many_to_many
81
+ ds.filter(r.qualified_right_primary_key=>sds.select(*Array(r.qualified_right_key)).
82
+ join(r[:join_table], r[:left_keys].zip(r[:left_primary_keys]), :implicit_qualifier=>model.table_name))
83
+ when :many_through_many
84
+ fre = r.reverse_edges.first
85
+ fe, *edges = r.edges
86
+ sds = sds.select(*Array(r.qualify(fre[:table], fre[:left]))).
87
+ join(fe[:table], Array(fe[:right]).zip(Array(fe[:left])), :implicit_qualifier=>model.table_name)
88
+ edges.each{|e| sds = sds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
89
+ ds.filter(r.qualified_right_primary_key=>sds)
90
+ else
91
+ raise Error, "unrecognized association type for association #{name.inspect}: #{r[:type].inspect}"
92
+ end
93
+ ds = model.apply_association_dataset_opts(r, ds)
94
+ r[:extend].each{|m| ds.extend(m)}
95
+ ds
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,58 @@
1
+ module Sequel
2
+ module Plugins
3
+ # DefaultsSetter is a simple plugin that sets non-nil/NULL default values upon
4
+ # initialize:
5
+ #
6
+ # # column a default NULL
7
+ # # column b default 2
8
+ # album = Album.new.values # {:b => 2}
9
+ # album = Album.new(:a=>1, :b=>3).values # {:a => 1, :b => 3}
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Make all model subclass instances set defaults (called before loading subclasses)
14
+ # Sequel::Model.plugin :defaults_setter
15
+ #
16
+ # # Make the Album class set defaults
17
+ # Album.plugin :defaults_setter
18
+ module DefaultsSetter
19
+ # Set the default values based on the model schema
20
+ def self.configure(model)
21
+ model.send(:set_default_values)
22
+ end
23
+
24
+ module ClassMethods
25
+ # The default values to set in initialize for this model. A hash with column symbol
26
+ # keys and default values. If the default values respond to +call+, it will be called
27
+ # to get the value, otherwise the value will be used directly. You can manually modify
28
+ # this hash to set specific default values, by default the ones will be parsed from the database.
29
+ attr_reader :default_values
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
37
+
38
+ private
39
+
40
+ def set_default_values
41
+ h = {}
42
+ @db_schema.each{|k, v| h[k] = v[:ruby_default] if v[:ruby_default]} if @db_schema
43
+ @default_values = h
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ private
49
+
50
+ # Set default values if they are not already set by the hash provided to initialize.
51
+ def initialize_set(h)
52
+ super
53
+ model.default_values.each{|k,v| self[k] = (v.respond_to?(:call) ? v.call : v) unless values.has_key?(k)}
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -8,7 +8,7 @@ module Sequel
8
8
  # attribute, the resulting value is forced to a given encoding if the
9
9
  # value is a string. There are two ways to specify the encoding. You
10
10
  # can either do so in the plugin call itself, or via the
11
- # forced_encoding class accessor:
11
+ # forced_encoding class accessor.
12
12
  #
13
13
  # Usage:
14
14
  #
@@ -46,6 +46,11 @@ module Sequel
46
46
  super(force_hash_encoding(row))
47
47
  end
48
48
 
49
+ # Force the encoding of all string values when setting the instance's values.
50
+ def set_values(row)
51
+ super(force_hash_encoding(row))
52
+ end
53
+
49
54
  private
50
55
 
51
56
  # Force the encoding for all string values in the given row hash.
@@ -55,11 +60,6 @@ module Sequel
55
60
  row
56
61
  end
57
62
 
58
- # Force the encoding of all string values when setting the instance's values.
59
- def set_values(row)
60
- super(force_hash_encoding(row))
61
- end
62
-
63
63
  # Force the encoding of all returned strings to the model's forced_encoding.
64
64
  def typecast_value(column, value)
65
65
  s = super
@@ -22,10 +22,7 @@ module Sequel
22
22
  # Identity maps are thread-local and only persist for the duration of the block,
23
23
  # so they should only be considered as a possible performance enhancer.
24
24
  #
25
- # The identity_map plugin is not compatible with the standard eager loading of
26
- # many_to_many and many_through_many associations. If you want to use the identity_map plugin,
27
- # you should use +eager_graph+ instead of +eager+ for those associations. It is also
28
- # not compatible with the eager loading in the +rcte_tree+ plugin.
25
+ # The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
29
26
  #
30
27
  # Usage:
31
28
  #
@@ -37,6 +34,116 @@ module Sequel
37
34
  # # would need to do Album.with_identity_map{} to use the identity map
38
35
  module IdentityMap
39
36
  module ClassMethods
37
+ # Override the default :eager_loader option for many_*_many associations to work
38
+ # with an identity_map. If the :eager_graph association option is used, you'll probably have to use
39
+ # :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
40
+ # mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
41
+ # getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
42
+ # they'll probably be lost. Making that work correctly would require changing a lot of the core
43
+ # architecture, such as how graphing and eager graphing work.
44
+ def associate(type, name, opts = {}, &block)
45
+ if opts[:eager_loader]
46
+ super
47
+ elsif type == :many_to_many
48
+ opts = super
49
+ el = opts[:eager_loader]
50
+ model = self
51
+ left_pk = opts[:left_primary_key]
52
+ uses_lcks = opts[:uses_left_composite_keys]
53
+ uses_rcks = opts[:uses_right_composite_keys]
54
+ right = opts[:right_key]
55
+ join_table = opts[:join_table]
56
+ left = opts[:left_key]
57
+ lcks = opts[:left_keys]
58
+ left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
59
+ opts[:eager_loader] = lambda do |eo|
60
+ return el.call(eo) unless model.identity_map
61
+ h = eo[:key_hash][left_pk]
62
+ eo[:rows].each{|object| object.associations[name] = []}
63
+ r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
64
+ l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
65
+
66
+ # Replace the row proc to remove the left key alias before calling the previous row proc.
67
+ # Associate the value of the left key alias with the associated object (through its object_id).
68
+ # When loading the associated objects, lookup the left key alias value and associate the
69
+ # associated objects to the main objects if the left key alias value matches the left primary key
70
+ # value of the main object.
71
+ #
72
+ # The deleting of the left key alias from the hash before calling the previous row proc
73
+ # is necessary when an identity map is used, otherwise if the same associated object is returned more than
74
+ # once for the association, it won't know which of current objects to associate it to.
75
+ ds = opts.associated_class.inner_join(join_table, r + l)
76
+ pr = ds.row_proc
77
+ h2 = {}
78
+ ds.row_proc = proc do |hash|
79
+ hash_key = if uses_lcks
80
+ left_key_alias.map{|k| hash.delete(k)}
81
+ else
82
+ hash.delete(left_key_alias)
83
+ end
84
+ obj = pr.call(hash)
85
+ (h2[obj.object_id] ||= []) << hash_key
86
+ obj
87
+ end
88
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
89
+ if hash_keys = h2.delete(assoc_record.object_id)
90
+ hash_keys.each do |hash_key|
91
+ if objects = h[hash_key]
92
+ objects.each{|object| object.associations[name].push(assoc_record)}
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ opts
99
+ elsif type == :many_through_many
100
+ opts = super
101
+ el = opts[:eager_loader]
102
+ model = self
103
+ left_pk = opts[:left_primary_key]
104
+ left_key = opts[:left_key]
105
+ uses_lcks = opts[:uses_left_composite_keys]
106
+ left_keys = Array(left_key)
107
+ left_key_alias = opts[:left_key_alias]
108
+ opts[:eager_loader] = lambda do |eo|
109
+ return el.call(eo) unless model.identity_map
110
+ h = eo[:key_hash][left_pk]
111
+ eo[:rows].each{|object| object.associations[name] = []}
112
+ ds = opts.associated_class
113
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
114
+ ft = opts[:final_reverse_edge]
115
+ conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
116
+
117
+ # See above comment in many_to_many eager_loader
118
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
119
+ pr = ds.row_proc
120
+ h2 = {}
121
+ ds.row_proc = proc do |hash|
122
+ hash_key = if uses_lcks
123
+ left_key_alias.map{|k| hash.delete(k)}
124
+ else
125
+ hash.delete(left_key_alias)
126
+ end
127
+ obj = pr.call(hash)
128
+ (h2[obj.object_id] ||= []) << hash_key
129
+ obj
130
+ end
131
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
132
+ if hash_keys = h2.delete(assoc_record.object_id)
133
+ hash_keys.each do |hash_key|
134
+ if objects = h[hash_key]
135
+ objects.each{|object| object.associations[name].push(assoc_record)}
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ opts
142
+ else
143
+ super
144
+ end
145
+ end
146
+
40
147
  # Returns the current thread-local identity map. Should be a hash if
41
148
  # there is an active identity map, and nil otherwise.
42
149
  def identity_map
@@ -56,7 +163,7 @@ module Sequel
56
163
  # certain fields in an initial query, make modifications to some of those
57
164
  # fields and request other, potentially overlapping fields in a new query,
58
165
  # and not have the second query override fields you modified.
59
- def load(row)
166
+ def call(row)
60
167
  return super unless idm = identity_map
61
168
  if o = idm[identity_map_key(Array(primary_key).map{|x| row[x]})]
62
169
  o.merge_db_update(row)
@@ -121,9 +228,9 @@ module Sequel
121
228
  # key option has a value and the association uses the primary key of
122
229
  # the associated class as the :primary_key option, check the identity
123
230
  # map for the associated object and return it if present.
124
- def _load_associated_objects(opts)
231
+ def _load_associated_objects(opts, dynamic_opts={})
125
232
  klass = opts.associated_class
126
- if klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts[:primary_key] == klass.primary_key and
233
+ if !dynamic_opts[:callback] && klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts.primary_key == klass.primary_key and
127
234
  opts[:key] and pk = _associated_object_pk(opts[:key]) and o = idm[klass.identity_map_key(pk)]
128
235
  o
129
236
  else