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
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"
@@ -237,6 +237,23 @@ Sequel::Model:
237
237
 
238
238
  class Invoice < Sequel::Model
239
239
  many_to_one :client
240
+
241
+ # has_one :through equivalent 1
242
+ # eager load with :eager=>:firm option on :client association, and eager loading :client
243
+ def firm
244
+ client.firm if client
245
+ end
246
+
247
+ # has_one :through equivalent 2
248
+ # eager load the usual way
249
+ many_to_many :firms, :join_table=>:clients, :left_key=>:id, :left_primary_key=>:client_id, :right_key=>:firm_id
250
+ def firm
251
+ firms.first
252
+ end
253
+
254
+ # has_one :through equivalent 3
255
+ # eager loading requires custom :eager_loader proc
256
+ many_to_one :firm, :dataset=>proc{Firm.join(:clients, :firm_id=>:id, :id=>client_id).select_all(:firms)}
240
257
  end
241
258
 
242
259
  Firm.first.invoices
@@ -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.
1348
+
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:
1225
1351
 
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.
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.
@@ -1300,6 +1430,78 @@ by the foreign/primary keys. This option causes the
1300
1430
  you want to replace the default foreign/primary key conditions that Sequel
1301
1431
  would use when eagerly graphing.
1302
1432
 
1433
+ === Column Naming Conflict Options
1434
+
1435
+ Sequel's association support historically called methods on model objects
1436
+ to get primary key or foreign key values instead of accessing the column
1437
+ values directly, in order to allow advanced features such as associations
1438
+ based on virtual column keys. Unfortunately, that causes issues if columns
1439
+ are used with names that clash with existing method names, which can happen
1440
+ if you want to name the association the same name as an existing column, or
1441
+ if the column has the same name as an already defined method such as
1442
+ <tt>object_id</tt>.
1443
+
1444
+ Sequel has added the following options that allow you to work around the
1445
+ issue by either specifying the column name symbol or the method name symbol
1446
+ to use. In most cases, these methods are designed to be used with column
1447
+ aliases defined with <tt>Model.def_column_alias</tt>:
1448
+
1449
+ # Example schema:
1450
+ # albums artists
1451
+ # :id /--> :id
1452
+ # :artist --/ :name
1453
+ # :name
1454
+ class Album < Sequel::Model
1455
+ def_column_alias(:artist_id, :artist)
1456
+ many_to_one :artist, :key=>:artist_id, :key_column=>:artist
1457
+ end
1458
+
1459
+ # Example schema:
1460
+ # things objs
1461
+ # :id /--> :id
1462
+ # :object_id --/ :name
1463
+ # :name
1464
+ class Thing < Sequel::Model
1465
+ def_column_alias(:obj_id, :object_id)
1466
+ end
1467
+ class Obj < Sequel::Model
1468
+ many_to_one :things, :key=>:object_id, :key_method=>:obj_id
1469
+ end
1470
+
1471
+ Note that for eager loading purposes, you generally need to set the
1472
+ <tt>:eager_loader_key</tt> option as well, specifying the underlying
1473
+ column(s) to use.
1474
+
1475
+ ==== :key_column [+many_to_one+]
1476
+
1477
+ Like the :key option, but :key references the method name, while
1478
+ :key_column references the underlying column.
1479
+
1480
+ ==== :primary_key_method [+many_to_one+]
1481
+
1482
+ Like the :primary_key option, but :primary_key references the column
1483
+ name, while :primary_key_method references the method name.
1484
+
1485
+ ==== :primary_key_column [+one_to_many+, +one_to_one+]
1486
+
1487
+ Like the :primary_key option, but :primary_key references the method name, while
1488
+ :primary_key_column references the underlying column.
1489
+
1490
+ ==== :key_method [+one_to_many+, +one_to_one+]
1491
+
1492
+ Like the :key option, but :key references the column
1493
+ name, while :key_method references the method name.
1494
+
1495
+ ==== :left_primary_key_column [+many_to_many+]
1496
+
1497
+ Like the :left_primary_key option, but :left_primary_key references the method name, while
1498
+ :left_primary_key_column references the underlying column.
1499
+
1500
+ ==== :right_primary_key_method [+many_to_many+]
1501
+
1502
+ Like the :right_primary_key option, but :right_primary_key references the column
1503
+ name, while :right_primary_key_method references the method name.
1504
+
1303
1505
  === Advanced Options
1304
1506
 
1305
1507
  ==== :reciprocal
@@ -1399,3 +1601,51 @@ associations depends on another plugin that defines instance methods of
1399
1601
  the same name. In that case, the instance methods of the dependent
1400
1602
  plugin would override the association methods created by the main
1401
1603
  plugin.
1604
+
1605
+ ==== :eager_limit_strategy
1606
+
1607
+ This setting determines what strategy to use for loading the associations
1608
+ that use the :limit setting to limit the number of returned records. You
1609
+ can't use LIMIT directly, since you want a limit for each associated
1610
+ record, not a LIMIT on the number of records returned by the dataset.
1611
+
1612
+ By default, no strategy is used for one_to_one associations, and
1613
+ the :ruby strategy is used for *_many associations, which does a simple
1614
+ array slice after loading the associated records. That doesn't provide
1615
+ a performance advantage, since all records are still loaded from the
1616
+ database, but it at least makes sure the cached records are accurately
1617
+ limited as they would be in the lazy load case.
1618
+
1619
+ The reason no strategy is used by default for one_to_one associations
1620
+ is that none is needed for a true one_to_one association (since there
1621
+ is only one associated record per current record). However, if you are
1622
+ using a one_to_one association where the relationship is really one_to_many,
1623
+ and using an order to pick the first matching row, then if you don't
1624
+ specify an :eager_limit_strategy option, you'll be loading all related
1625
+ rows just to have Sequel ignore all rows after the first. By using a
1626
+ strategy to change the query to only return one associated record per
1627
+ current record, you can get much better database performance.
1628
+
1629
+ You can set a value of +true+ for this option to have Sequel select what
1630
+ it thinks is the best way of limiting the records for your database. You
1631
+ can also specify a symbol to manually choose a strategy. The available
1632
+ strategies are:
1633
+
1634
+ :distinct_on :: Uses DISTINCT ON to ensure only the first matching record
1635
+ is loaded (one_to_one associations only). This is used by
1636
+ default on PostgreSQL.
1637
+ :window_function :: Uses window functions if the database supports it.
1638
+ This is used by default on databases that support window
1639
+ functions.
1640
+ :correlated_subquery :: Uses a correlated subquery to get the information.
1641
+ This is never used by default as if you aren't careful,
1642
+ it can result in pathologically long running times.
1643
+ This will not work correctly for associations where
1644
+ the associated table has a composite primary key if
1645
+ the database doesn't support using IN
1646
+ with multiple columns. This will also not work on MySQL
1647
+ because MySQL has problems using IN with a correlated
1648
+ subquery that contains a limit.
1649
+ :ruby :: Uses ruby array slicing to emulate database limiting (*_many
1650
+ associations only). This is the default if the database doesn't
1651
+ 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
+