sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -73
data/Rakefile CHANGED
@@ -1,11 +1,5 @@
1
1
  require "rake"
2
2
  require "rake/clean"
3
- require "rake/gempackagetask"
4
- begin
5
- require "hanna/rdoctask"
6
- rescue LoadError
7
- require "rake/rdoctask"
8
- end
9
3
 
10
4
  NAME = 'sequel'
11
5
  VERS = lambda do
@@ -13,14 +7,13 @@ VERS = lambda do
13
7
  Sequel.version
14
8
  end
15
9
  CLEAN.include ["**/.*.sw?", "sequel-*.gem", ".config", "rdoc", "coverage", "www/public/*.html", "www/public/rdoc*", '**/*.rbc']
16
- RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
17
- RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
18
10
 
19
11
  # Gem Packaging and Release
20
12
 
21
13
  desc "Packages sequel"
22
14
  task :package=>[:clean] do |p|
23
- sh %{gem build sequel.gemspec}
15
+ load './sequel.gemspec'
16
+ Gem::Builder.new(SEQUEL_GEMSPEC).build
24
17
  end
25
18
 
26
19
  desc "Install sequel gem"
@@ -40,7 +33,20 @@ end
40
33
 
41
34
  ### RDoc
42
35
 
43
- Rake::RDocTask.new do |rdoc|
36
+ RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
37
+
38
+ rdoc_task_class = begin
39
+ require "rdoc/task"
40
+ RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
41
+ RDoc::Task
42
+ rescue LoadError
43
+ require "rake/rdoctask"
44
+ Rake::RDocTask
45
+ end
46
+
47
+ RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
48
+
49
+ rdoc_task_class.new do |rdoc|
44
50
  rdoc.rdoc_dir = "rdoc"
45
51
  rdoc.options += RDOC_OPTS
46
52
  rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
@@ -50,25 +56,25 @@ end
50
56
 
51
57
  desc "Make local version of website"
52
58
  task :website do
53
- sh %{www/make_www.rb}
59
+ sh %{#{FileUtils::RUBY} www/make_www.rb}
54
60
  end
55
61
 
56
62
  desc "Make rdoc for website"
57
63
  task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
58
64
 
59
- Rake::RDocTask.new(:website_rdoc_main) do |rdoc|
65
+ rdoc_task_class.new(:website_rdoc_main) do |rdoc|
60
66
  rdoc.rdoc_dir = "www/public/rdoc"
61
67
  rdoc.options += RDOC_OPTS
62
68
  rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
63
69
  end
64
70
 
65
- Rake::RDocTask.new(:website_rdoc_adapters) do |rdoc|
71
+ rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
66
72
  rdoc.rdoc_dir = "www/public/rdoc-adapters"
67
73
  rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
68
74
  rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
69
75
  end
70
76
 
71
- Rake::RDocTask.new(:website_rdoc_plugins) do |rdoc|
77
+ rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
72
78
  rdoc.rdoc_dir = "www/public/rdoc-plugins"
73
79
  rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
74
80
  rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
@@ -92,11 +98,13 @@ begin
92
98
  require "spec/rake/spectask"
93
99
  spec_class = Spec::Rake::SpecTask
94
100
  spec_files_meth = :spec_files=
101
+ spec_opts_meth = :spec_opts=
95
102
  rescue LoadError
96
103
  # RSpec 2
97
104
  require "rspec/core/rake_task"
98
105
  spec_class = RSpec::Core::RakeTask
99
106
  spec_files_meth = :pattern=
107
+ spec_opts_meth = :rspec_opts=
100
108
  end
101
109
 
102
110
  spec = lambda do |name, files, d|
@@ -105,25 +113,35 @@ begin
105
113
  desc d
106
114
  spec_class.new(name) do |t|
107
115
  t.send spec_files_meth, files
108
- t.spec_opts = ENV['SEQUEL_SPEC_OPTS'].split if ENV['SEQUEL_SPEC_OPTS']
116
+ t.send spec_opts_meth, ENV['SEQUEL_SPEC_OPTS'].split if ENV['SEQUEL_SPEC_OPTS']
109
117
  end
110
118
  end
111
119
 
112
- spec_with_cov = lambda do |name, files, d|
120
+ sc = spec_with_cov = lambda do |name, files, d|
113
121
  spec.call(name, files, d)
114
122
  t = spec.call("#{name}_cov", files, "#{d} with coverage")
115
123
  t.rcov = true
116
124
  t.rcov_opts = File.read("spec/rcov.opts").split("\n")
125
+ t
126
+ end
127
+
128
+ if RUBY_VERSION >= '1.8.7'
129
+ eval <<-END
130
+ spec_with_cov = lambda do |*x, &b|
131
+ t = sc.call(*x)
132
+ b.call(t) if b
133
+ end
134
+ END
117
135
  end
118
136
 
119
137
  task :default => [:spec]
120
- spec_with_cov.call("spec", Dir["spec/{core,model}/*_spec.rb"], "Run core and model specs")
138
+ spec_with_cov.call("spec", Dir["spec/{core,model}/*_spec.rb"], "Run core and model specs"){|t| t.rcov_opts.concat(%w'--exclude "lib/sequel/adapters/([a-ln-z]|m[a-np-z])"')}
121
139
  spec.call("spec_core", Dir["spec/core/*_spec.rb"], "Run core specs")
122
140
  spec.call("spec_model", Dir["spec/model/*_spec.rb"], "Run model specs")
123
141
  spec_with_cov.call("spec_plugin", Dir["spec/extensions/*_spec.rb"], "Run extension/plugin specs")
124
142
  spec_with_cov.call("spec_integration", Dir["spec/integration/*_test.rb"], "Run integration tests")
125
143
 
126
- %w'postgres sqlite mysql informix oracle firebird mssql'.each do |adapter|
144
+ %w'postgres sqlite mysql informix oracle firebird mssql db2'.each do |adapter|
127
145
  spec_with_cov.call("spec_#{adapter}", ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"], "Run #{adapter} specs")
128
146
  end
129
147
  rescue LoadError
@@ -154,5 +172,5 @@ end
154
172
 
155
173
  desc "Check syntax of all .rb files"
156
174
  task :check_syntax do
157
- Dir['**/*.rb'].each{|file| print `#{ENV['RUBY'] || :ruby} -c #{file} | fgrep -v "Syntax OK"`}
175
+ Dir['**/*.rb'].each{|file| print `#{FileUtils::RUBY} -c #{file} | fgrep -v "Syntax OK"`}
158
176
  end
data/bin/sequel CHANGED
@@ -112,11 +112,7 @@ end
112
112
 
113
113
  connect_proc = lambda do |database|
114
114
  db = if database.nil? || database.empty?
115
- db = Sequel::Database.new(:quote_identifiers=>false)
116
- def db.connect(*args); Object.new; end
117
- db.identifier_input_method = nil
118
- db.identifier_output_method = nil
119
- db
115
+ Sequel.connect('mock:///', db_opts)
120
116
  elsif File.exist?(database)
121
117
  require 'yaml'
122
118
  env ||= "development"
@@ -247,7 +247,7 @@ example is a tree structure:
247
247
 
248
248
  class Node
249
249
  many_to_one :parent, :class=>self
250
- one_to_many :children :key=>:parent_id, :class=>self
250
+ one_to_many :children, :key=>:parent_id, :class=>self
251
251
  end
252
252
 
253
253
  For many_to_many self_referential associations, it's fairly similar. Here's
@@ -293,32 +293,6 @@ Examples:
293
293
  @artist.remove_album(@album)
294
294
  @artist.remove_all_albums
295
295
 
296
- == Dataset Method
297
-
298
- In addition to the above methods, associations also add a instance method
299
- ending in +_dataset+ that returns a dataset representing the objects in the associated table:
300
-
301
- @album.artist_id
302
- # 10
303
- @album.artist_dataset
304
- # SELECT * FROM artists WHERE (id = 10)
305
-
306
- @artist.id
307
- # 20
308
- @artist.albums_dataset
309
- # SELECT * FROM albums WHERE (artist_id = 20)
310
-
311
- The association dataset is just like any other Sequel dataset, in that
312
- it can be further filtered, ordered, etc.:
313
-
314
- @artist.albums_dataset.
315
- filter(:name.like('A%')).
316
- order(:copies_sold).
317
- limit(10)
318
- # SELECT * FROM albums
319
- # WHERE ((artist_id = 20) AND (name LIKE 'A%'))
320
- # ORDER BY copies_sold LIMIT 10
321
-
322
296
  == Caching
323
297
 
324
298
  Associations are cached after being retrieved:
@@ -352,13 +326,150 @@ instance method:
352
326
  @album.artists # [<Artist ...>, ...]
353
327
  @album.associations[:artists] # [<Artist ...>, ...]
354
328
 
355
- Note that while the association method caches associated objects, if you
356
- retrieve access through the association dataset directly, the results
357
- will not be cached:
329
+ == Dataset Method
330
+
331
+ In addition to the above methods, associations also add a instance method
332
+ ending in +_dataset+ that returns a dataset representing the objects in the associated table:
333
+
334
+ @album.artist_id
335
+ # 10
336
+ @album.artist_dataset
337
+ # SELECT * FROM artists WHERE (id = 10)
338
+
339
+ @artist.id
340
+ # 20
341
+ @artist.albums_dataset
342
+ # SELECT * FROM albums WHERE (artist_id = 20)
343
+
344
+ The association dataset is just like any other Sequel dataset, in that
345
+ it can be further filtered, ordered, etc.:
346
+
347
+ @artist.albums_dataset.
348
+ filter(:name.like('A%')).
349
+ order(:copies_sold).
350
+ limit(10)
351
+ # SELECT * FROM albums
352
+ # WHERE ((artist_id = 20) AND (name LIKE 'A%'))
353
+ # ORDER BY copies_sold LIMIT 10
354
+
355
+ Records retrieved using the +_dataset+ method are not cached in the
356
+ associations cache.
358
357
 
359
358
  @album.artists_dataset.all # [<Artist ...>, ...]
360
359
  @album.associations[:artists] # nil
361
360
 
361
+ == Dynamic Association Modification
362
+
363
+ Similar to the +_dataset+ method, you can provide a block to the association
364
+ method to customize the dataset that will be used to retrieve the records. So
365
+ you can apply a filter in either of these two ways:
366
+
367
+ @artist.albums_dataset.filter(:name.like('A%'))
368
+ @artist.albums{|ds| ds.filter(:name.like('A%'))}
369
+
370
+ While they both apply the same filter, using the +_dataset+ method does not
371
+ apply any of the association callbacks or handle association reciprocals (see
372
+ below for details about callbacks and reciprocals). Using a block instead handles
373
+ all those things, and also caches its results in the associations cache (ignoring
374
+ any previously cached value).
375
+
376
+ Note that if you are using ruby 1.8.6, you can't pass a block to the association
377
+ method, you have to pass a proc as an argument:
378
+
379
+ @artist.albums(proc{|ds| ds.filter(:name.like('A%'))})
380
+
381
+ == Filtering By Associations
382
+
383
+ In addition to using the association method to get associated objects, you
384
+ can also use associated objects in filters. For example, to get
385
+ all albums for a given artist, you would usually do:
386
+
387
+ @artist.albums
388
+ # or @artist.albums_dataset for a dataset
389
+
390
+ You can also do the following:
391
+
392
+ Album.filter(:artist=>@artist).all
393
+ # or leave off the .all for a dataset
394
+
395
+ For filtering by a single association, this isn't very useful. However, unlike
396
+ using the association method, using a filter allows you to filter by multiple
397
+ associations:
398
+
399
+ Album.filter(:artist=>@artist, :publisher=>@publisher)
400
+
401
+ This will return all albums by that artist and published by that publisher.
402
+ This isn't possible using just the association method approach, though you
403
+ can combine the approaches:
404
+
405
+ @artist.albums_dataset.filter(:publisher=>@publisher)
406
+
407
+ This doesn't just work for +many_to_one+ associations, it also works for
408
+ +one_to_one+, +one_to_many+, and +many_to_many+ associations:
409
+
410
+ Album.one_to_one :album_info
411
+ # The album related to that AlbumInfo instance
412
+ Album.filter(:album_info=>AlbumInfo[2])
413
+
414
+ Album.one_to_many :tracks
415
+ # The album related to that Track instance
416
+ Album.filter(:tracks=>Track[3])
417
+
418
+ Album.many_to_many :tags
419
+ # All albums related to that Tag instance
420
+ Album.filter(:tags=>Tag[4])
421
+
422
+ Note that for +one_to_many+ and +many_to_many+ associations, you still
423
+ use the plural form even though only a single model object is given.
424
+
425
+ You can also exclude by associations:
426
+
427
+ Album.exclude(:artist=>@artist).all
428
+
429
+ This will return all albums not by that artist.
430
+
431
+ You can also provide an array with multiple model objects:
432
+
433
+ Album.filter(:artist=>[@artist1, @artist2]).all
434
+
435
+ Similar to using an array of integers or strings, this will return
436
+ all albums whose artist is one of those two artists. You can also
437
+ use +exclude+ if you want all albums not by either of those artists:
438
+
439
+ Album.exclude(:artist=>[@artist1, @artist2]).all
440
+
441
+ If you are using a +one_to_many+ or +many_to_many+ association, you
442
+ may want to return records where the records matches all of multiple
443
+ records, instead of matching any of them. For example:
444
+
445
+ Album.filter(:tags=>[@tag1, @tag2])
446
+
447
+ This matches albums that are associated with either @tag1 or @tag2 or
448
+ both. If you only want ones that you are associated with both, you can
449
+ use separate filter calls:
450
+
451
+ Album.filter(:tags=>@tag1).filter(:tags=>@tag2)
452
+
453
+ Or the the array form of condition specifiers:
454
+
455
+ Album.filter([[:tags, @tag1], [:tags, @tag2]])
456
+
457
+ These will return albums associated with both @tag1 and @tag2.
458
+
459
+ You can also provide a dataset value when filtering by associations:
460
+
461
+ Album.filter(:artist=>Artist.filter(:name.like('A%'))).all
462
+
463
+ This will return all albums whose artist starts with 'A'. Like
464
+ the other forms, this can be inverted:
465
+
466
+ Album.exclude(:artist=>Artist.filter(:name.like('A%'))).all
467
+
468
+ This will return all albums whose artist does not start with 'A'.
469
+
470
+ Note that filtering by associations only works correctly for simple
471
+ associations (ones without conditions).
472
+
362
473
  == Name Collisions
363
474
 
364
475
  Because associations create instance methods, it's possible to override
@@ -368,7 +479,7 @@ would be bad association names.
368
479
 
369
480
  == Database Schema
370
481
 
371
- Creating an association, doesn't modify the database schema. Sequel
482
+ Creating an association doesn't modify the database schema. Sequel
372
483
  assumes your associations reflect the existing database schema. If not,
373
484
  you should modify your schema before creating the associations.
374
485
 
@@ -1055,7 +1166,7 @@ when eager loading via eager_graph, but called when eager loading via eager.
1055
1166
  class Artist
1056
1167
  # Cache all album names to a single string when retrieving the
1057
1168
  # albums.
1058
- one_to_many :albums, :after_add=>:cache_album_names
1169
+ one_to_many :albums, :after_load=>:cache_album_names
1059
1170
 
1060
1171
  attr_reader :album_names
1061
1172
 
@@ -1218,25 +1329,44 @@ a JOIN USING or NATURAL JOIN for the graph:
1218
1329
  Artist.one_to_many :albums, :key=>:artist_name,
1219
1330
  :graph_only_conditions=>nil, :graph_join_type=>:natural
1220
1331
 
1332
+ ==== :graph_alias_base
1333
+
1334
+ The base name to use for the table alias when eager graphing. Defaults to the name
1335
+ of the association. If the alias name has already been used in the query, Sequel will create
1336
+ a unique alias by appending a numeric suffix (e.g. alias_0, alias_1, ...) until the alias is
1337
+ unique.
1338
+
1339
+ This is mostly useful if you have associations with the same name in many models, and you want
1340
+ to be able to easily tell which table alias corresponds to which association when eagerly
1341
+ graphing multiple associations with the same name.
1342
+
1221
1343
  ==== :eager_grapher
1222
1344
 
1223
1345
  Sets up a custom grapher to use when eager loading the objects via eager_graph.
1224
- This is the eager_graph analogue to the :eager_loader option.
1346
+ This is the eager_graph analogue to the :eager_loader option. This isn't generally
1347
+ needed, as one of the other eager_graph related association options is usually sufficient.
1225
1348
 
1226
- If specified, should be a proc that accepts three three arguments: a dataset,
1227
- an alias to use for the table to graph for this association, and the alias that
1228
- was used for the current table (since you can cascade associations). Should
1229
- return a modified copy of the dataset with the association graphed into it.
1349
+ If specified, should be a proc that accepts one or three three arguments.
1350
+ If the proc takes one argument, it will be given a hash with the following keys:
1351
+
1352
+ :self :: The dataset that is doing the eager loading
1353
+ :table_alias :: An alias to use for the table to graph for this association.
1354
+ :implicit_qualifier :: The alias that was used for the current table (since you can cascade associations).
1355
+ :callback :: A callback proc used to dynamically modify the dataset to graph into the
1356
+ current dataset, before such graphing is done. This is nil if no callback
1357
+ proc is used.
1358
+
1359
+ If the proc takes three arguments, it gets passed the :self, :association_alias,
1360
+ and :table_alias values. The 3 argument procs are allowed for backwards
1361
+ compatibility, and it is recommended to use the 1 argument proc format
1362
+ for new code.
1230
1363
 
1231
1364
  Artist.one_to_many :self_title_albums, :class=>:Album,
1232
- :eager_grapher=>(proc do |ds, ta, iq|
1233
- ds.graph(Album, {:artist_id=>:id, :name=>:name},
1234
- :table_alias=>ta, :implicit_qualifier=>iq)
1365
+ :eager_grapher=>(proc do |eo|
1366
+ eo[:self].graph(Album, {:artist_id=>:id, :name=>:name},
1367
+ :table_alias=>eo[:table_alias], :implicit_qualifier=>eo[:implicit_qualifier])
1235
1368
  end)
1236
1369
 
1237
- This isn't generally needed, as one of the other eager_graph related
1238
- association options is usually sufficient.
1239
-
1240
1370
  ==== :order_eager_graph
1241
1371
 
1242
1372
  Whether to add the order to the dataset's order when graphing via eager_graph.
@@ -1353,6 +1483,34 @@ If the default modification methods would not do what you want, and you
1353
1483
  don't plan on overriding the internal modification methods to do what you
1354
1484
  want, it may be best to set this option to true.
1355
1485
 
1486
+ ==== :key_column [+many_to_one+]
1487
+
1488
+ This option exists to make it possible to create a +many_to_one+ association
1489
+ with the same name as the foreign key it uses. Before this option was
1490
+ introduced, this was problematic, since the :key option was used to refer
1491
+ both to the model methods and the underlying database columns (which are
1492
+ usually the same).
1493
+
1494
+ If you have a legacy database with a foreign key that is the same as the name
1495
+ of the association you would like to use, you should create a column alias
1496
+ for the foreign key and use this option:
1497
+
1498
+ # Example schema:
1499
+ # albums artists
1500
+ # :id /--> :id
1501
+ # :artist --/ :name
1502
+ # :name
1503
+
1504
+ class Album < Sequel::Model
1505
+ def_column_alias(:artist_id, :artist)
1506
+ many_to_one :artist, :key_column=>:artist
1507
+ end
1508
+
1509
+ Note that if you have control over the database, it's better to just rename
1510
+ the foreign key column than use this option. Similarly, if you can think
1511
+ of another association name that will work, it's wiser to rename the
1512
+ association than use this option. Think of this option as a last resort.
1513
+
1356
1514
  ==== :validate
1357
1515
 
1358
1516
  Set to false to not validate when implicitly saving any associated object.
@@ -1399,3 +1557,51 @@ associations depends on another plugin that defines instance methods of
1399
1557
  the same name. In that case, the instance methods of the dependent
1400
1558
  plugin would override the association methods created by the main
1401
1559
  plugin.
1560
+
1561
+ ==== :eager_limit_strategy
1562
+
1563
+ This setting determines what strategy to use for loading the associations
1564
+ that use the :limit setting to limit the number of returned records. You
1565
+ can't use LIMIT directly, since you want a limit for each associated
1566
+ record, not a LIMIT on the number of records returned by the dataset.
1567
+
1568
+ By default, no strategy is used for one_to_one associations, and
1569
+ the :ruby strategy is used for *_many associations, which does a simple
1570
+ array slice after loading the associated records. That doesn't provide
1571
+ a performance advantage, since all records are still loaded from the
1572
+ database, but it at least makes sure the cached records are accurately
1573
+ limited as they would be in the lazy load case.
1574
+
1575
+ The reason no strategy is used by default for one_to_one associations
1576
+ is that none is needed for a true one_to_one association (since there
1577
+ is only one associated record per current record). However, if you are
1578
+ using a one_to_one association where the relationship is really one_to_many,
1579
+ and using an order to pick the first matching row, then if you don't
1580
+ specify an :eager_limit_strategy option, you'll be loading all related
1581
+ rows just to have Sequel ignore all rows after the first. By using a
1582
+ strategy to change the query to only return one associated record per
1583
+ current record, you can get much better database performance.
1584
+
1585
+ You can set a value of +true+ for this option to have Sequel select what
1586
+ it thinks is the best way of limiting the records for your database. You
1587
+ can also specify a symbol to manually choose a strategy. The available
1588
+ strategies are:
1589
+
1590
+ :distinct_on :: Uses DISTINCT ON to ensure only the first matching record
1591
+ is loaded (one_to_one associations only). This is used by
1592
+ default on PostgreSQL.
1593
+ :window_function :: Uses window functions if the database supports it.
1594
+ This is used by default on databases that support window
1595
+ functions.
1596
+ :correlated_subquery :: Uses a correlated subquery to get the information.
1597
+ This is never used by default as if you aren't careful,
1598
+ it can result in pathologically long running times
1599
+ This will not work correctly for association where
1600
+ the associated table has a composite primary key if
1601
+ the database doesn't support using IN
1602
+ with multiple columns. This will also not work on MySQL
1603
+ because MySQL has problems using IN with a correlated
1604
+ subquery that contains a limit.
1605
+ :ruby :: Uses ruby array slicing to emulate database limiting (*_many
1606
+ associations only). This is the default if the database doesn't
1607
+ support window functions.
@@ -74,12 +74,12 @@ Most Dataset methods that users will use can be broken down into two types:
74
74
 
75
75
  Most dataset methods fall into this category, which can be further broken down by the clause they affect:
76
76
 
77
- SELECT:: select, select_all, select_append, select_more
77
+ SELECT:: select, select_all, select_append, select_group, select_more
78
78
  FROM:: from, from_self
79
79
  JOIN:: join, left_join, right_join, full_join, natural_join, natural_left_join, natural_right_join, natural_full_join, cross_join, inner_join, left_outer_join, right_outer_join, full_outer_join, join_table
80
- WHERE:: where, filter, exclude, and, or, grep, invert, unfiltered
81
- GROUP:: group, group_by, group_and_count, ungrouped
82
- HAVING:: having, filter, exclude, and, or, grep, invert, unfiltered
80
+ WHERE:: where, filter, exclude, exclude_where, and, or, grep, invert, unfiltered
81
+ GROUP:: group, group_by, group_and_count, select_group, ungrouped
82
+ HAVING:: having, filter, exclude, exclude_having, and, or, grep, invert, unfiltered
83
83
  ORDER:: order, order_by, order_append, order_prepend, order_more, reverse, reverse_order, unordered
84
84
  LIMIT:: limit, unlimited
85
85
  compounds:: union, intersect, except
@@ -59,6 +59,14 @@ Ranges (both inclusive and exclusive) can also be used:
59
59
  items.filter(:price => 100...200).sql
60
60
  #=> "SELECT * FROM items WHERE (price >= 100 AND price < 200)"
61
61
 
62
+ == Filtering using an array
63
+
64
+ If you need to select multiple items from a dataset, you can supply an array:
65
+
66
+ item_array = [1, 38, 47, 99]
67
+ items.filter(:id => item_array).sql
68
+ #=> "SELECT * FROM items WHERE (id IN (1, 38, 47, 99))"
69
+
62
70
  == Filtering using expressions
63
71
 
64
72
  Sequel allows you to use ruby expressions directly in the call to filter:
@@ -0,0 +1,54 @@
1
+ = Sequel::Model Mass Assignment
2
+
3
+ Most Model methods that take a hash of attribute keys and values, including <tt>Model.new</tt>, <tt>Model.create</tt>, <tt>Model#set</tt> and <tt>Model#update</tt> are subject to Sequel's mass assignment rules.
4
+ When you pass a hash to these methods, each key has an <tt>=</tt> appended to it (the setter method), and if the setter method exists and access to it is not restricted, Sequel will call the setter method with the hash value.
5
+ By default, there are two types of setter methods that are restricted.
6
+ The first is methods like <tt>typecast_on_assignment=</tt> and <tt>==</tt>, which don't affect columns.
7
+ These methods cannot be enabled for mass assignment.
8
+ The second is primary key setters.
9
+ To enable use of primary key setters, you need to call +unrestrict_primary_key+ for that model:
10
+
11
+ Post.unrestrict_primary_key
12
+
13
+ Since mass assignment by default allows modification of all column values except for primary key columns, it can be a security risk in some cases.
14
+ Sequel has multiple ways of securing mass assignment.
15
+ The first way is using +set_allowed_columns+:
16
+
17
+ Post.set_allowed_columns :title, :body, :category
18
+
19
+ This explicitly sets which methods are allowed (<tt>title=</tt>, <tt>body=</tt>, and <tt>category=</tt>), all other methods will not be allowed.
20
+ This method is useful in simple applications where the same columns are allowed in all cases, but not appropriate when different columns are allowed in different scenarios (e.g. admin access vs. user access).
21
+ To handle cases where different columns are allowed in different cases, you can use +set_only+ or +update_only+:
22
+
23
+ # user case
24
+ post.set_only(params[:post], :title, :body)
25
+ # admin case
26
+ post.set_only(params[:post], :title, :body, :deleted)
27
+
28
+ In this case, only the <tt>title=</tt> and <tt>body=</tt> methods will be allowed in the mass assignment.
29
+
30
+ By default, if an invalid setter method call is attempted, Sequel raises a <tt>Sequel::Error</tt> exception. You can have Sequel silently ignore invalid calls by doing:
31
+
32
+ # Global default
33
+ Sequel::Model.strict_param_setting = false
34
+ # Class level
35
+ Post.strict_param_setting = false
36
+ # Instance level
37
+ post.strict_param_setting = false
38
+
39
+ These mass assignment methods have been around a long time, but starting in Sequel 3.12.0, the +set_fields+ or +update_fields+ methods were added, and these may be a better mass assignment choice for most users.
40
+ These methods take two arguments, the attributes hash as the first argument, and a single array of valid field names as the second argument:
41
+
42
+ post.set_fields(params[:post], [:title, :body])
43
+
44
+ +set_fields+ and +update_fields+ differ in implementation from +set_only+ and +update_only+.
45
+ With +set_only+ and +update_only+, the hash is iterated over and it checks each method call attempt to see if it is valid.
46
+ With +set_fields+ and +update_fields+, the array is iterated over, and it just looks up the value in the hash and calls the appropriate setter method.
47
+
48
+ +set_fields+ and +update_fields+ are designed for the case where you are expecting specific fields in the input, and want to ignore the other fields.
49
+ They work great for things like HTML forms where the form fields are static.
50
+ +set_only+ and +update_only+ are designed for cases where you are not sure what fields are going to be present in the input, but still want to make sure only certain setter methods can be called.
51
+ They work great for flexible APIs.
52
+
53
+ In all of the mass assignment cases, methods starting with +set+ will set the attributes without saving the object, while methods starting with +update+ will set the attributes and then save the changes to the object.
54
+