sequel 3.12.1 → 3.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG +42 -0
  2. data/README.rdoc +137 -118
  3. data/Rakefile +21 -66
  4. data/doc/active_record.rdoc +9 -9
  5. data/doc/advanced_associations.rdoc +59 -188
  6. data/doc/association_basics.rdoc +15 -2
  7. data/doc/cheat_sheet.rdoc +38 -33
  8. data/doc/dataset_filtering.rdoc +16 -7
  9. data/doc/prepared_statements.rdoc +7 -7
  10. data/doc/querying.rdoc +5 -4
  11. data/doc/release_notes/3.13.0.txt +210 -0
  12. data/doc/sharding.rdoc +1 -1
  13. data/doc/sql.rdoc +5 -5
  14. data/doc/validations.rdoc +11 -11
  15. data/lib/sequel/adapters/ado.rb +1 -1
  16. data/lib/sequel/adapters/do.rb +3 -3
  17. data/lib/sequel/adapters/firebird.rb +3 -3
  18. data/lib/sequel/adapters/jdbc/h2.rb +39 -0
  19. data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
  20. data/lib/sequel/adapters/jdbc/oracle.rb +3 -3
  21. data/lib/sequel/adapters/mysql.rb +7 -4
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/shared/mssql.rb +10 -1
  24. data/lib/sequel/adapters/shared/mysql.rb +63 -0
  25. data/lib/sequel/adapters/shared/postgres.rb +61 -3
  26. data/lib/sequel/adapters/sqlite.rb +105 -18
  27. data/lib/sequel/connection_pool.rb +31 -30
  28. data/lib/sequel/core.rb +58 -58
  29. data/lib/sequel/core_sql.rb +52 -43
  30. data/lib/sequel/database/misc.rb +11 -0
  31. data/lib/sequel/database/query.rb +55 -17
  32. data/lib/sequel/dataset/actions.rb +2 -1
  33. data/lib/sequel/dataset/query.rb +2 -3
  34. data/lib/sequel/dataset/sql.rb +24 -11
  35. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  36. data/lib/sequel/metaprogramming.rb +4 -0
  37. data/lib/sequel/model.rb +37 -19
  38. data/lib/sequel/model/associations.rb +33 -25
  39. data/lib/sequel/model/base.rb +2 -2
  40. data/lib/sequel/model/plugins.rb +7 -2
  41. data/lib/sequel/plugins/active_model.rb +1 -1
  42. data/lib/sequel/plugins/association_pks.rb +2 -2
  43. data/lib/sequel/plugins/association_proxies.rb +1 -1
  44. data/lib/sequel/plugins/boolean_readers.rb +2 -2
  45. data/lib/sequel/plugins/class_table_inheritance.rb +10 -2
  46. data/lib/sequel/plugins/identity_map.rb +3 -3
  47. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  48. data/lib/sequel/plugins/json_serializer.rb +212 -0
  49. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  50. data/lib/sequel/plugins/list.rb +174 -0
  51. data/lib/sequel/plugins/many_through_many.rb +2 -2
  52. data/lib/sequel/plugins/rcte_tree.rb +6 -7
  53. data/lib/sequel/plugins/tree.rb +118 -0
  54. data/lib/sequel/plugins/xml_serializer.rb +321 -0
  55. data/lib/sequel/sql.rb +315 -206
  56. data/lib/sequel/timezones.rb +40 -17
  57. data/lib/sequel/version.rb +8 -2
  58. data/spec/adapters/firebird_spec.rb +2 -2
  59. data/spec/adapters/informix_spec.rb +1 -1
  60. data/spec/adapters/mssql_spec.rb +2 -2
  61. data/spec/adapters/mysql_spec.rb +2 -2
  62. data/spec/adapters/oracle_spec.rb +1 -1
  63. data/spec/adapters/postgres_spec.rb +36 -6
  64. data/spec/adapters/spec_helper.rb +2 -2
  65. data/spec/adapters/sqlite_spec.rb +1 -1
  66. data/spec/core/connection_pool_spec.rb +3 -3
  67. data/spec/core/core_sql_spec.rb +31 -13
  68. data/spec/core/database_spec.rb +39 -2
  69. data/spec/core/dataset_spec.rb +24 -12
  70. data/spec/core/expression_filters_spec.rb +5 -1
  71. data/spec/core/object_graph_spec.rb +1 -1
  72. data/spec/core/schema_generator_spec.rb +1 -1
  73. data/spec/core/schema_spec.rb +1 -1
  74. data/spec/core/spec_helper.rb +1 -1
  75. data/spec/core/version_spec.rb +1 -1
  76. data/spec/extensions/active_model_spec.rb +82 -67
  77. data/spec/extensions/association_dependencies_spec.rb +1 -1
  78. data/spec/extensions/association_pks_spec.rb +1 -1
  79. data/spec/extensions/association_proxies_spec.rb +1 -1
  80. data/spec/extensions/blank_spec.rb +1 -1
  81. data/spec/extensions/boolean_readers_spec.rb +1 -1
  82. data/spec/extensions/caching_spec.rb +1 -1
  83. data/spec/extensions/class_table_inheritance_spec.rb +3 -2
  84. data/spec/extensions/composition_spec.rb +2 -5
  85. data/spec/extensions/force_encoding_spec.rb +3 -1
  86. data/spec/extensions/hook_class_methods_spec.rb +1 -1
  87. data/spec/extensions/identity_map_spec.rb +1 -1
  88. data/spec/extensions/inflector_spec.rb +1 -1
  89. data/spec/extensions/instance_filters_spec.rb +1 -1
  90. data/spec/extensions/instance_hooks_spec.rb +1 -1
  91. data/spec/extensions/json_serializer_spec.rb +154 -0
  92. data/spec/extensions/lazy_attributes_spec.rb +1 -2
  93. data/spec/extensions/list_spec.rb +251 -0
  94. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  95. data/spec/extensions/many_through_many_spec.rb +3 -3
  96. data/spec/extensions/migration_spec.rb +1 -1
  97. data/spec/extensions/named_timezones_spec.rb +5 -6
  98. data/spec/extensions/nested_attributes_spec.rb +1 -1
  99. data/spec/extensions/optimistic_locking_spec.rb +1 -1
  100. data/spec/extensions/pagination_spec.rb +1 -1
  101. data/spec/extensions/pretty_table_spec.rb +1 -1
  102. data/spec/extensions/query_spec.rb +1 -1
  103. data/spec/extensions/rcte_tree_spec.rb +1 -1
  104. data/spec/extensions/schema_dumper_spec.rb +3 -2
  105. data/spec/extensions/schema_spec.rb +1 -1
  106. data/spec/extensions/serialization_spec.rb +6 -2
  107. data/spec/extensions/sharding_spec.rb +1 -1
  108. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  109. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  110. data/spec/extensions/spec_helper.rb +7 -3
  111. data/spec/extensions/sql_expr_spec.rb +1 -1
  112. data/spec/extensions/string_date_time_spec.rb +1 -1
  113. data/spec/extensions/string_stripper_spec.rb +1 -1
  114. data/spec/extensions/subclasses_spec.rb +1 -1
  115. data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
  116. data/spec/extensions/thread_local_timezones_spec.rb +1 -1
  117. data/spec/extensions/timestamps_spec.rb +1 -1
  118. data/spec/extensions/touch_spec.rb +1 -1
  119. data/spec/extensions/tree_spec.rb +119 -0
  120. data/spec/extensions/typecast_on_load_spec.rb +1 -1
  121. data/spec/extensions/update_primary_key_spec.rb +1 -1
  122. data/spec/extensions/validation_class_methods_spec.rb +1 -1
  123. data/spec/extensions/validation_helpers_spec.rb +1 -1
  124. data/spec/extensions/xml_serializer_spec.rb +142 -0
  125. data/spec/integration/associations_test.rb +1 -1
  126. data/spec/integration/database_test.rb +1 -1
  127. data/spec/integration/dataset_test.rb +29 -14
  128. data/spec/integration/eager_loader_test.rb +1 -1
  129. data/spec/integration/migrator_test.rb +1 -1
  130. data/spec/integration/model_test.rb +1 -1
  131. data/spec/integration/plugin_test.rb +316 -1
  132. data/spec/integration/prepared_statement_test.rb +1 -1
  133. data/spec/integration/schema_test.rb +8 -8
  134. data/spec/integration/spec_helper.rb +1 -1
  135. data/spec/integration/timezone_test.rb +1 -1
  136. data/spec/integration/transaction_test.rb +35 -20
  137. data/spec/integration/type_test.rb +1 -1
  138. data/spec/model/association_reflection_spec.rb +1 -1
  139. data/spec/model/associations_spec.rb +49 -34
  140. data/spec/model/base_spec.rb +1 -1
  141. data/spec/model/dataset_methods_spec.rb +4 -4
  142. data/spec/model/eager_loading_spec.rb +1 -1
  143. data/spec/model/hooks_spec.rb +1 -1
  144. data/spec/model/inflector_spec.rb +1 -1
  145. data/spec/model/model_spec.rb +7 -1
  146. data/spec/model/plugins_spec.rb +1 -1
  147. data/spec/model/record_spec.rb +1 -3
  148. data/spec/model/spec_helper.rb +2 -2
  149. data/spec/model/validations_spec.rb +1 -1
  150. metadata +29 -5
data/Rakefile CHANGED
@@ -89,82 +89,37 @@ end
89
89
  begin
90
90
  require "spec/rake/spectask"
91
91
 
92
- spec_opts = lambda do
93
- lib_dir = File.join(File.dirname(__FILE__), 'lib')
92
+ spec = lambda do |name, files, d|
93
+ lib_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lib')
94
94
  ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)
95
+ desc d
96
+ Spec::Rake::SpecTask.new(name) do |t|
97
+ t.spec_files = files
98
+ t.spec_opts = ENV['SEQUEL_SPEC_OPTS'].split if ENV['SEQUEL_SPEC_OPTS']
99
+ end
95
100
  end
96
101
 
97
- rcov_opts = lambda do
98
- [true, File.read("spec/rcov.opts").split("\n")]
99
- end
100
-
101
- desc "Run core and model specs with coverage"
102
- Spec::Rake::SpecTask.new("spec_coverage") do |t|
103
- t.spec_files = Dir["spec/{core,model}/*_spec.rb"]
104
- spec_opts.call
105
- t.rcov, t.rcov_opts = rcov_opts.call
102
+ spec_with_cov = lambda do |name, files, d|
103
+ spec.call(name, files, d)
104
+ t = spec.call("#{name}_cov", files, "#{d} with coverage")
105
+ t.rcov = true
106
+ t.rcov_opts = File.read("spec/rcov.opts").split("\n")
106
107
  end
107
108
 
108
- desc "Run core and model specs"
109
109
  task :default => [:spec]
110
- Spec::Rake::SpecTask.new("spec") do |t|
111
- t.spec_files = Dir["spec/{core,model}/*_spec.rb"]
112
- spec_opts.call
113
- end
114
-
115
- desc "Run core specs"
116
- Spec::Rake::SpecTask.new("spec_core") do |t|
117
- t.spec_files = Dir["spec/core/*_spec.rb"]
118
- spec_opts.call
119
- end
120
-
121
- desc "Run model specs"
122
- Spec::Rake::SpecTask.new("spec_model") do |t|
123
- t.spec_files = Dir["spec/model/*_spec.rb"]
124
- spec_opts.call
125
- end
126
-
127
- desc "Run extension/plugin specs"
128
- Spec::Rake::SpecTask.new("spec_plugin") do |t|
129
- t.spec_files = Dir["spec/extensions/*_spec.rb"]
130
- spec_opts.call
131
- end
132
-
133
- desc "Run extention/plugin specs with coverage"
134
- Spec::Rake::SpecTask.new("spec_plugin_cov") do |t|
135
- t.spec_files = Dir["spec/extensions/*_spec.rb"]
136
- spec_opts.call
137
- t.rcov, t.rcov_opts = rcov_opts.call
138
- end
139
-
140
- desc "Run integration tests"
141
- Spec::Rake::SpecTask.new("integration") do |t|
142
- t.spec_files = Dir["spec/integration/*_test.rb"]
143
- spec_opts.call
144
- end
145
-
146
- desc "Run integration tests with coverage"
147
- Spec::Rake::SpecTask.new("integration_cov") do |t|
148
- t.spec_files = Dir["spec/integration/*_test.rb"]
149
- spec_opts.call
150
- t.rcov, t.rcov_opts = rcov_opts.call
151
- end
110
+ spec_with_cov.call("spec", Dir["spec/{core,model}/*_spec.rb"], "Run core and model specs")
111
+ spec.call("spec_core", Dir["spec/core/*_spec.rb"], "Run core specs")
112
+ spec.call("spec_model", Dir["spec/model/*_spec.rb"], "Run model specs")
113
+ spec_with_cov.call("spec_plugin", Dir["spec/extensions/*_spec.rb"], "Run extension/plugin specs")
114
+ spec_with_cov.call("spec_integration", Dir["spec/integration/*_test.rb"], "Run integration tests")
152
115
 
153
116
  %w'postgres sqlite mysql informix oracle firebird mssql'.each do |adapter|
154
- desc "Run #{adapter} specs"
155
- Spec::Rake::SpecTask.new("spec_#{adapter}") do |t|
156
- t.spec_files = ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"]
157
- spec_opts.call
158
- end
159
-
160
- desc "Run #{adapter} specs with coverage"
161
- Spec::Rake::SpecTask.new("spec_#{adapter}_cov") do |t|
162
- t.spec_files = ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"]
163
- spec_opts.call
164
- t.rcov, t.rcov_opts = rcov_opts.call
165
- end
117
+ spec_with_cov.call("spec_#{adapter}", ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"], "Run #{adapter} specs")
166
118
  end
167
119
  rescue LoadError
120
+ task :default do
121
+ puts "Must install rspec to run the default task (which runs specs)"
122
+ end
168
123
  end
169
124
 
170
125
  desc "check documentation coverage"
@@ -21,7 +21,7 @@ Sequel will autogenerate the column accessors, so you can do:
21
21
  album = Album.new
22
22
  album.name = 'RF'
23
23
 
24
- If the table name for the class doesn't match the default one Sequel to use, you can specify it manually:
24
+ If the table name for the class doesn't match the default one Sequel will choose, you can specify it manually:
25
25
 
26
26
  class Album < Sequel::Model(:records)
27
27
  end
@@ -61,7 +61,7 @@ Sequel's +validation_class_methods+ plugin is modeled directly on ActiveRecord's
61
61
 
62
62
  == Hooks/Callbacks
63
63
 
64
- Sequel's +hook_class_methods+ plugin is modeled directly on ActiveRecord's callbacks, but the recommended approach is to define you hooks/callbacks as instance methods:
64
+ Sequel's +hook_class_methods+ plugin is modeled directly on ActiveRecord's callbacks, but the recommended approach is to define your hooks/callbacks as instance methods:
65
65
 
66
66
  class Album < Sequel::Model
67
67
  def before_create
@@ -98,7 +98,7 @@ Sequel supports both single table inheritance and class table inheritance using
98
98
 
99
99
  == Transactions
100
100
 
101
- Sequel supports transactions via the Database object (which we recommend using the DB constant for single database applications):
101
+ Sequel supports transactions via the Database object (we recommend using the DB constant for single database applications):
102
102
 
103
103
  DB.transaction do
104
104
  album.artist.num_albums -= 1
@@ -132,7 +132,7 @@ Just like ActiveRecord, Sequel doesn't use sessions, it lets you modify objects
132
132
 
133
133
  == Database Abstraction
134
134
 
135
- Sequel supports far more database abstraction than ActiveRecord, and setting up the database connection is easy:
135
+ Sequel supports far more database abstractions than ActiveRecord, and setting up the database connection is easy:
136
136
 
137
137
  DB = Sequel.sqlite # memory database
138
138
  DB = Sequel.connect('postgres://user:pass@host/database') # connection string
@@ -218,7 +218,7 @@ This is because LIKE is case sensitive on PostgreSQL, but case insensitive on My
218
218
 
219
219
  This will do a case insensitive search on both databases. If you want a case sensitive search on both, you can use +like+ instead of +ilike+.
220
220
 
221
- String concatention is a similar area, where MySQL and PostgreSQL handle things differently. With Sequel, the same code can work on both databases:
221
+ String concatenation is a similar area, where MySQL and PostgreSQL handle things differently. With Sequel, the same code can work on both databases:
222
222
 
223
223
  Album.select(:name.sql_string + ' - Name')
224
224
 
@@ -302,7 +302,7 @@ Sequel doesn't have a <tt>has_many :through</tt> association, instead you can us
302
302
 
303
303
  Sequel doesn't come with support for polymorphic associations. Using polymorphic associations is generally bad from a database design perspective, as it violates referential integrity. If you have an old database and must have polymorphic associations, there is an external +sequel_polymorphic+ plugin that can handle them, just by using the standard association options provided by Sequel.
304
304
 
305
- Sequel doesn't directly support creating a bunch of associated objects and delaying saving them to the database when the main object with save, like you can with the <tt>association.build</tt> methods in ActiveRecord. You can use +before_save or +after_save+ hooks, or the +nested_attributes+ or +instance_hooks+ plugins to get similar support.
305
+ Sequel doesn't directly support creating a bunch of associated objects and delaying saving them to the database until the main object is saved, like you can with the <tt>association.build</tt> methods in ActiveRecord. You can use +before_save or +after_save+ hooks, or the +nested_attributes+ or +instance_hooks+ plugins to get similar support.
306
306
 
307
307
  Sequel supports the same basic association hooks/callbacks as ActiveRecord. It also supports <tt>:after_load</tt>, which is run after the associated objects are loaded. For <tt>*_to_one</tt> associations, it supports +before_set+ and +after_set+ hooks, since a setter method is used instead of an add/remove method pair.
308
308
 
@@ -442,7 +442,7 @@ This part of the guide will list Sequel equivalents for ActiveRecord methods, ho
442
442
 
443
443
  ==== +abstract_class+, <tt>abstract_class=</tt>, <tt>abstract_class?</tt>
444
444
 
445
- With Sequel, these methods don't exist because it is it doesn't default to using single table inheritance in subclasses. ActiveRecord assumes that subclasses of Model classes use single table inheritance, and you have to set <tt>abstract_class = true</tt> to use an abstract class. In Sequel, you must use the +single_table_inheritance+ or +class_tabble_inheritance+ plugin to configure inheritance in the database.
445
+ With Sequel, these methods don't exist because it doesn't default to using single table inheritance in subclasses. ActiveRecord assumes that subclasses of Model classes use single table inheritance, and you have to set <tt>abstract_class = true</tt> to use an abstract class. In Sequel, you must use the +single_table_inheritance+ or +class_tabble_inheritance+ plugin to configure inheritance in the database.
446
446
 
447
447
  ==== +all+
448
448
 
@@ -479,7 +479,7 @@ Note that +test_connection+ will return true if a connection can be made, but wi
479
479
 
480
480
  ==== +connection+
481
481
 
482
- Sequel only uses connections for the minimum about of time necessary, checking them out to do a query, and returning them as soon as the query finishes. If you do want direct access to the connection object:
482
+ Sequel only uses connections for the minimum amount of time necessary, checking them out to do a query, and returning them as soon as the query finishes. If you do want direct access to the connection object:
483
483
 
484
484
  Sequel::Model.db.synchronize do |connection|
485
485
  ...
@@ -667,7 +667,7 @@ Note that +update+ in that case will operate on a dataset, so it won't run model
667
667
 
668
668
  ==== +with_scope+
669
669
 
670
- Sequel works a little differently than with_scope. Instead of using nested blocks, you just use a cleaner method chain. +with_scope+ is often used an an around_filter or similar construct, where in Sequel, you would just need to assign do a dataset in a before filter, and use that dataset in the action.
670
+ Sequel works a little differently than with_scope. Instead of using nested blocks, you just use a cleaner method chain. +with_scope+ is often used as an around_filter or similar construct, where in Sequel, you would just need to assign to a dataset in a before filter, and use that dataset in the action.
671
671
 
672
672
  === Class Methods with Roughly the Same Behavior
673
673
 
@@ -2,43 +2,41 @@
2
2
 
3
3
  Sequel::Model has the most powerful and flexible associations of any ruby ORM.
4
4
 
5
- "Extraordinary claims require extraordinary proof" - Carl Sagan
6
-
7
5
  == Background: Sequel::Model association options
8
6
 
9
7
  There are a bunch of advanced association options that are available to
10
- handle the other-than-bog-standard cases. First we'll go over some of
8
+ handle more complex cases. First we'll go over some of
11
9
  the simpler ones:
12
10
 
13
11
  All associations take a block that can be used to further filter/modify the
14
12
  default dataset. There's also an :eager_block option if you want to use
15
- a different block when eager loading via Dataset#eager. Association blocks are
13
+ a different block when eager loading via <tt>Dataset#eager</tt>. Association blocks are
16
14
  useful for things like:
17
15
 
18
16
  Artist.one_to_many :gold_albums, :class=>:Album do |ds|
19
- ds.filter{|o| o.copies_sold > 500000}
17
+ ds.filter{copies_sold > 500000}
20
18
  end
21
19
 
22
20
  There are a whole bunch of options for changing how the association is eagerly
23
- loaded via Dataset#eager_graph: :graph_block, :graph_conditions,
24
- :graph_only_conditions, :graph_join_type (and :graph_join_table_* ones for
21
+ loaded via <tt>Dataset#eager_graph</tt>: <tt>:graph_block</tt>, <tt>:graph_conditions</tt>,
22
+ <tt>:graph_only_conditions</tt>, <tt>:graph_join_type</tt> (and <tt>:graph_join_table_*</tt> ones for
25
23
  JOINing to the join table in a many_to_many association).
26
24
 
27
- - :graph_join_type - The type of join to do
28
- - :graph_conditions - Additional conditions to put on join (needs to be a
29
- hash or array of all two pairs). Automatically assumes unqualified symbols
30
- as first element of the pair to be columns of the associated model, and
31
- unqualified symbols of the second element of the pair to be columns of the
32
- current model.
33
- - :graph_block - A block passed to join_table, allowing you to specify
34
- conditions other than equality, or to use OR, or set up any arbitrary
35
- condition. The block is passed the associated table alias, current model
36
- alias, and array of previous joins.
37
- - :graph_only_conditions - Use these conditions instead of the standard
38
- association conditions. This is necessary when you don't want to have an
39
- equal condition between the foreign key and primary key of the tables.
40
- You can also use this to have a JOIN USING (array of symbols), or a NATURAL
41
- or CROSS JOIN (nil, with the appropriate :graph_join_type).
25
+ :graph_join_type :: The type of join to do (<tt>:inner</tt>, <tt>:left</tt>, <tt>:right</tt>)
26
+ :graph_conditions :: Additional conditions to put on join (needs to be a
27
+ hash or array of all two pairs). Automatically assumes unqualified symbols
28
+ or first element of the pair to be columns of the associated model, and
29
+ unqualified symbols of the second element of the pair to be columns of the
30
+ current model.
31
+ :graph_block :: A block passed to +join_table+, allowing you to specify
32
+ conditions other than equality, or to use OR, or set up any arbitrary
33
+ condition. The block is passed the associated table alias, current table
34
+ alias, and an array of previous joins clause objects.
35
+ :graph_only_conditions :: Use these conditions instead of the standard
36
+ association conditions. This is necessary when you don't want to have an
37
+ equal condition between the foreign key and primary key of the tables.
38
+ You can also use this to have a JOIN USING (array of symbols), or a NATURAL
39
+ or CROSS JOIN (nil, with the appropriate <tt>:graph_join_type</tt>).
42
40
 
43
41
  These can be used like this:
44
42
 
@@ -54,7 +52,7 @@ These can be used like this:
54
52
  Artist.one_to_many :gold_albums, :class=>:Album, \
55
53
  :graph_block=>proc{|j,lj,js| :copies_sold.qualify(j) > 500000}
56
54
 
57
- # Handles the case where the to tables are associated by a case insensitive name string
55
+ # Handles the case where the tables are associated by a case insensitive name string
58
56
  Artist.one_to_many :albums, :key=>:artist_name, \
59
57
  :graph_only_conditions=>nil, \
60
58
  :graph_block=>proc{|j,lj,js| {:lower.sql_function(artist_name.qualify(j))=>:lower.sql_function(name.qualify(lj))}}
@@ -63,15 +61,15 @@ These can be used like this:
63
61
  # a JOIN USING
64
62
  Artist.one_to_many :albums, :key=>:artist_name, :graph_only_conditions=>[:artist_name]
65
63
 
66
- Remember, using #eager_graph is generally only necessary when you need to
64
+ Remember, using +eager_graph+ is generally only necessary when you need to
67
65
  filter/order based on columns in an associated table, it is recommended to
68
- use #eager for eager loading if possible.
66
+ use +eager+ for eager loading if possible.
69
67
 
70
- For lazy loading (e.g. Model[1].association), the :dataset option can be used
68
+ For lazy loading (e.g. Model[1].association), the <tt>:dataset</tt> option can be used
71
69
  to specify an arbitrary dataset (one that uses different keys, multiple keys,
72
70
  joins to other tables, etc.).
73
71
 
74
- For eager loading via #eager, the :eager_loader option can be used to specify
72
+ For eager loading via +eager+, the <tt>:eager_loader</tt> option can be used to specify
75
73
  how to eagerly load a complex association. This is an extremely powerful
76
74
  option. Though it can often be verbose (compared to other things in Sequel),
77
75
  it allows you complete control over how to eagerly load associations for a
@@ -94,13 +92,13 @@ for new code.
94
92
  Since you are given all of the records, you can do things like filter on
95
93
  associations that are specified by multiple keys, or do multiple
96
94
  queries depending on the content of the records (which would be
97
- necessary for polymorphic associations). Inside the :eager_loader
95
+ necessary for polymorphic associations). Inside the <tt>:eager_loader</tt>
98
96
  proc, you should get the related objects and populate the
99
97
  associations cache for all objects in the array of records. The hash
100
98
  of dependent associations is available for you to cascade the eager
101
99
  loading down multiple levels, but it is up to you to use it. The
102
100
  key_hash is a performance enhancement that is used by the default
103
- code and is also available to you. It is a hash with keys being
101
+ association loaders and is also available to you. It is a hash with keys being
104
102
  foreign/primary key symbols in the current table, and the values
105
103
  being hashes where the key is foreign/primary key values and values
106
104
  being arrays of current model objects having the foreign/primary key
@@ -115,38 +113,36 @@ give an example:
115
113
  # The key_hash provided to the :eager_loader proc would be:
116
114
  {:id=>{1=>[album1], 3=>[album2]}, :artist_id=>{2=>[album1, album2]}}
117
115
 
118
- Using these options, you can build associations Sequel doesn't natively support,
116
+ Using these options, you can build associations that Sequel doesn't natively support,
119
117
  and still be able to use the same eager loading features that you are used to.
120
118
 
121
119
  == ActiveRecord associations
122
120
 
123
- Sequel supports all of associations that ActiveRecord supports, one way or
124
- another. Sometimes this requires more code, as Sequel is a toolkit and not
125
- a swiss army chainsaw.
121
+ Sequel supports all of associations that ActiveRecord supports, though some
122
+ require different approaches or custom <tt>:eager_loader</tt> options.
126
123
 
127
124
  === Association callbacks
128
125
 
129
- Sequel supports the same callbacks that ActiveRecord does on one_to_many and
130
- many_to_many associations: :before_add, :before_remove, :after_add, and
131
- :after_remove. One many_to_one associations and one_to_one associations
132
- (which are one_to_many associations with the :one_to_one option), Sequel
133
- supports the :before_set and :after_set callbacks. On all associations,
134
- Sequel supports :after_load, which is called after the association has been
126
+ Sequel supports the same callbacks that ActiveRecord does on +one_to_many+ and
127
+ +many_to_many+ associations: <tt>:before_add</tt>, <tt>:before_remove</tt>, <tt>:after_add</tt>, and
128
+ <tt>:after_remove</tt>. One +many_to_one+ associations and +one_to_one+ associations, Sequel
129
+ supports the <tt>:before_set</tt> and <tt>:after_set</tt> callbacks. On all associations,
130
+ Sequel supports <tt>:after_load</tt>, which is called after the association has been
135
131
  loaded.
136
132
 
137
- Each of these options can be a Symbol specifying an instance method
138
- that takes one argument (the associated object), or a Proc that takes
133
+ Each of these options can be a symbol specifying an instance method
134
+ that takes one argument (the associated object), or a proc that takes
139
135
  two arguments (the current object and the associated object), or an
140
- array of Symbols and Procs. For :after_load with a *_to_many association,
136
+ array of symbols and procs. For <tt>:after_load</tt> with a *_to_many association,
141
137
  the associated object argument is an array of associated objects.
142
138
 
143
- If any of the before callbacks return false, the adding/removing
144
- does not happen and it either raises an error (the default), or
145
- returns nil (if raise_on_save_failure is false).
139
+ If any of the before callbacks return +false+, the adding/removing
140
+ does not happen and it either raises a <tt>Sequel::BeforeHookFailed</tt> (the default), or
141
+ returns false (if +raise_on_save_failure+ is false).
146
142
 
147
143
  === Association extensions
148
144
 
149
- All associations come with an association_dataset method that can be further filtered or
145
+ All associations come with an <tt><i>association</i>_dataset</tt> method that can be further filtered or
150
146
  otherwise modified:
151
147
 
152
148
  class Author < Sequel::Model
@@ -154,10 +150,10 @@ otherwise modified:
154
150
  end
155
151
  Author.first.authorships_dataset.filter{|o| o.number < 10}.first
156
152
 
157
- You can extend a dataset with a module easily with :extend. You can reference
153
+ You can extend a dataset with a module using the <tt>:extend</tt> association option. You can reference
158
154
  the model object that created the association dataset via the dataset's
159
- model_object method, and the related association reflection via the dataset's
160
- association_reflection method:
155
+ +model_object+ method, and the related association reflection via the dataset's
156
+ +association_reflection+ method:
161
157
 
162
158
  module FindOrCreate
163
159
  def find_or_create(vals)
@@ -169,11 +165,11 @@ association_reflection method:
169
165
  end
170
166
  Author.first.authorships_dataset.find_or_create(:name=>'Blah', :number=>10)
171
167
 
172
- === has_many :through associations
168
+ === <tt>has_many :through</tt> associations
173
169
 
174
- many_to_many handles the usual case of a has_many :through with a belongs_to in
170
+ +many_to_many+ handles the usual case of a <tt>has_many :through</tt> with a +belongs_to+ in
175
171
  the associated model. It doesn't break on the case where the join table is a
176
- model table, unlike ActiveRecord's has_and_belongs_to_many.
172
+ model table, unlike ActiveRecord's +has_and_belongs_to_many+.
177
173
 
178
174
  ActiveRecord:
179
175
 
@@ -205,8 +201,8 @@ Sequel::Model:
205
201
  @author = Author.first
206
202
  @author.books
207
203
 
208
- If you use an association other than belongs_to in the associated model, you'll have
209
- to specify some of the :*key options and write a short method.
204
+ If you use an association other than +belongs_to+ in the associated model, such as a +has_many+,
205
+ you still use a +many_to_many+ association, but you need to use some options:
210
206
 
211
207
  ActiveRecord:
212
208
 
@@ -241,10 +237,6 @@ Sequel::Model:
241
237
 
242
238
  class Invoice < Sequel::Model
243
239
  many_to_one :client
244
-
245
- def firm
246
- client.firm if client
247
- end
248
240
  end
249
241
 
250
242
  Firm.first.invoices
@@ -262,7 +254,8 @@ you are stuck with an existing design that uses them.
262
254
 
263
255
  If you must use them, look for the sequel_polymorphic plugin, as it makes using
264
256
  polymorphic associations in Sequel about as easy as it is in ActiveRecord. However,
265
- here's how they can be done using Sequel's custom associations:
257
+ here's how they can be done using Sequel's custom associations (the sequel_polymorphic
258
+ plugin is just a generic version of this code):
266
259
 
267
260
  ActiveRecord:
268
261
 
@@ -362,75 +355,12 @@ Sequel::Model:
362
355
  @asset.attachable = @post
363
356
  @asset.attachable = @note
364
357
 
365
- == More advanced associations
366
-
367
- So far, we've only shown that Sequel::Model has associations as powerful as
368
- ActiveRecord's. Now we will show how Sequel::Model's associations are more
369
- powerful.
370
-
371
- === many_to_one/one_to_many not referencing primary key
372
-
373
- This can now be handled easily in Sequel using the :primary_key association
374
- option. However, this example shows how the association was possible before
375
- the introduction of that option.
376
-
377
- Let's say you have two tables, invoices and clients, where each client is
378
- associated with many invoices. However, instead of using the client's primary
379
- key, the invoice is associated to the client by name (this is bad database
380
- design, but sometimes you have to play with the cards you are dealt).
381
-
382
- class Client < Sequel::Model
383
- one_to_many :invoices, :reciprocal=>:client, \
384
- :dataset=>proc{Invoice.filter(:client_name=>name)}, \
385
- :eager_loader=>(proc do |eo|
386
- id_map = {}
387
- eo[:rows].each do |client|
388
- id_map[client.name] = client
389
- client.associations[:invoices] = []
390
- end
391
- Invoice.filter(:client_name=>id_map.keys.sort).all do |inv|
392
- inv.associations[:client] = client = id_map[inv.client_name]
393
- client.associations[:invoices] << inv
394
- end
395
- end)
396
-
397
- private
398
-
399
- def _add_invoice(invoice)
400
- invoice.client_name = name
401
- invoice.save
402
- end
403
- def _remove_invoice(invoice)
404
- invoice.client_name = nil
405
- invoice.save
406
- end
407
- def _remove_all_invoices
408
- Invoice.filter(:client_name=>name).update(:client_name=>nil)
409
- end
410
- end
411
-
412
- class Invoice < Sequel::Model
413
- many_to_one :client, :key=>:client_name, \
414
- :dataset=>proc{Client.filter(:name=>client_name)}, \
415
- :eager_loader=>(proc do |eo|
416
- id_map = eo[:key_hash][:client_name]
417
- eo[:rows].each{|inv| inv.associations[:client] = nil}
418
- Client.filter(:name=>id_map.keys).all do |client|
419
- id_map[client.name].each{|inv| inv.associations[:client] = client}
420
- end
421
- end)
422
-
423
- private
424
-
425
- def _client=(client)
426
- self.client_name = (client.name if client)
427
- end
428
- end
358
+ == Other advanced associations
429
359
 
430
360
  === Joining on multiple keys
431
361
 
432
362
  Let's say you have two tables that are associated with each other with multiple
433
- keys. This can now be handled using Sequel's built in composite key support for
363
+ keys. This can be handled using Sequel's built in composite key support for
434
364
  associations:
435
365
 
436
366
  # Both of these models have an album_id, number, and disc_number fields.
@@ -441,42 +371,7 @@ associations:
441
371
  many_to_one :favorite_track, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id]
442
372
  end
443
373
  class FavoriteTrack < Sequel::Model
444
- one_to_many :tracks, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id], :one_to_one=>true
445
- end
446
-
447
- Here's the old way to do it via custom associations:
448
-
449
- class Track < Sequel::Model
450
- many_to_one :favorite_track, \
451
- :dataset=>(proc do
452
- FavoriteTrack.filter(:disc_number=>disc_number, :number=>number, :album_id=>album_id)
453
- end), \
454
- :eager_loader=>(proc do |eo|
455
- id_map = {}
456
- eo[:rows].each do |t|
457
- t.associations[:favorite_track] = nil
458
- id_map[[t[:album_id], t[:disc_number], t[:number]]] = t
459
- end
460
- FavoriteTrack.filter([:album_id, :disc_number, :number]=>id_map.keys).all do |ft|
461
- if t = id_map[[ft[:album_id], ft[:disc_number], ft[:number]]]
462
- t.associations[:favorite_track] = ft
463
- end
464
- end
465
- end)
466
- end
467
-
468
- class FavoriteTrack < Sequel::Model
469
- many_to_one :track, \
470
- :dataset=>(proc do
471
- Track.filter(:disc_number=>disc_number, :number=>number, :album_id=>album_id)
472
- end), \
473
- :eager_loader=>(proc do |eo|
474
- id_map = {}
475
- eo[:rows].each{|ft| id_map[[ft[:album_id], ft[:disc_number], ft[:number]]] = ft}
476
- Track.filter([:album_id, :disc_number, :number]=>id_map.keys).all do |t|
477
- id_map[[t[:album_id], t[:disc_number], t[:number]]].associations[:track] = t
478
- end
479
- end)
374
+ one_to_one :tracks, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id]
480
375
  end
481
376
 
482
377
  === Tree - All Ancestors and Descendents
@@ -552,37 +447,13 @@ without knowing the depth of the tree?
552
447
  Note that unlike ActiveRecord, Sequel supports common table expressions, which allows you to use recursive queries.
553
448
  The results are not the same as in the above case, as all descendents are stored in a single association,
554
449
  but all descendants can be both lazy loaded or eager loaded in a single query (assuming your database
555
- supports recursive common table expressions):
450
+ supports recursive common table expressions). Sequel ships with an +rcte_tree+ plugin that makes
451
+ this easy:
556
452
 
557
453
  class Node < Sequel::Model
558
- one_to_many :descendants, :class=>Node, :dataset=>(proc do
559
- Node.from(:t).
560
- with_recursive(:t, Node.filter(:parent_id=>pk),
561
- Node.join(:t, :id=>:parent_id).
562
- select(:nodes.*))
563
- end),
564
- :eager_loader=>(proc do |eo|
565
- id_map = eo[:key_hash][:id]
566
- eo[:rows].each{|n| n.associations[:descendants] = []}
567
- Node.from(:t).
568
- with_recursive(:t, Node.filter(:parent_id=>id_map.keys).
569
- select(:parent_id___root, :id, :parent_id),
570
- Node.join(:t, :id=>:parent_id).
571
- select(:t__root, :nodes.*)).
572
- all.each do |node|
573
- if root = id_map[node.values.delete(:root)].first
574
- root.associations[:descendants] << node
575
- end
576
- end
577
- end)
454
+ plugin :rcte_tree
578
455
  end
579
456
 
580
- Sequel ships with an +rcte_tree+ plugin that allows simple creation
581
- of ancestors and descendants relationships that use recursive common
582
- table expressions:
583
-
584
- Node.plugin :rcte_tree
585
-
586
457
  === Joining multiple keys to a single key, through a third table
587
458
 
588
459
  Let's say you have a database, of songs, lyrics, and artists. Each song
@@ -654,6 +525,6 @@ associated tickets.
654
525
  end
655
526
 
656
527
  Note that it is often better to use a sum cache instead of this approach. You can implement
657
- a sum cache using after_create and after_delete hooks, or using a database trigger
528
+ a sum cache using +after_create+ and +after_delete+ hooks, or using a database trigger
658
529
  (the preferred method if you only have to support one database and that database supports
659
530
  triggers).