sequel 3.11.0 → 3.12.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 (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -19,6 +19,11 @@ module Sequel
19
19
  def count
20
20
  values.inject(0){|m, v| m + v.length}
21
21
  end
22
+
23
+ # Return true if there are no error messages, false otherwise.
24
+ def empty?
25
+ count == 0
26
+ end
22
27
 
23
28
  # Returns an array of fully-formatted error messages.
24
29
  def full_messages
@@ -32,7 +37,9 @@ module Sequel
32
37
  # Returns the array of errors for the given attribute, or nil
33
38
  # if there are no errors for the attribute.
34
39
  def on(att)
35
- self[att] if has_key?(att)
40
+ if v = fetch(att, nil) and !v.empty?
41
+ v
42
+ end
36
43
  end
37
44
  end
38
45
  end
@@ -7,6 +7,14 @@ module Sequel
7
7
  # allow the full support of Sequel::Model objects in Rails 3.
8
8
  # This plugin requires active_model in order to use
9
9
  # ActiveModel::Naming.
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Make all subclasses active_model compliant (called before loading subclasses)
14
+ # Sequel::Model.plugin :active_model
15
+ #
16
+ # # Make the Album class active_model compliant
17
+ # Album.plugin :active_model
10
18
  module ActiveModel
11
19
  ClassMethods = ::ActiveModel::Naming
12
20
 
@@ -0,0 +1,87 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The association_pks plugin adds the association_pks and association_pks=
4
+ # instance methods to the model class for each association added. These
5
+ # methods allow for easily returning the primary keys of the associated
6
+ # objects, and easily modifying the associated objects to set the primary
7
+ # keys to just the ones given:
8
+ #
9
+ # Artist.one_to_many :albums
10
+ # artist = Artist[1]
11
+ # artist.album_pks # [1, 2, 3]
12
+ # artist.album_pks = [2, 4]
13
+ # artist.album_pks # [2, 4]
14
+ #
15
+ # Note that it uses the singular form of the association name. Also note
16
+ # that the setter both associates to new primary keys not in the assocation
17
+ # and disassociated from primary keys not provided to the method.
18
+ #
19
+ # This plugin makes modifications directly to the underlying tables,
20
+ # it does not create or return any model objects, and therefore does
21
+ # not call any callbacks. If you have any association callbacks,
22
+ # you probably should not use the setter methods.
23
+ #
24
+ # This plugin only works with singular primary keys, it does not work
25
+ # with composite primary keys.
26
+ #
27
+ # Usage:
28
+ #
29
+ # # Make all model subclass *_to_many associations have association_pks
30
+ # # methods (called before loading subclasses)
31
+ # Sequel::Model.plugin :association_pks
32
+ #
33
+ # # Make the Album *_to_many associations have association_pks
34
+ # # methods (called before the association methods)
35
+ # Album.plugin :association_pks
36
+ module AssociationPks
37
+ module ClassMethods
38
+ private
39
+
40
+ # Define a association_pks method using the block for the association reflection
41
+ def def_association_pks_getter(opts, &block)
42
+ association_module_def(:"#{singularize(opts[:name])}_pks", &block)
43
+ end
44
+
45
+ # Define a association_pks= method using the block for the association reflection,
46
+ # if the association is not read only.
47
+ def def_association_pks_setter(opts, &block)
48
+ association_module_def(:"#{singularize(opts[:name])}_pks=", &block) unless opts[:read_only]
49
+ end
50
+
51
+ # Add a getter that checks the join table for matching records and
52
+ # a setter that deletes from or inserts into the join table.
53
+ def def_many_to_many(opts)
54
+ super
55
+ def_association_pks_getter(opts) do
56
+ _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key])).select_map(opts[:right_key])
57
+ end
58
+ def_association_pks_setter(opts) do |pks|
59
+ checked_transaction do
60
+ ds = _join_table_dataset(opts).filter(opts[:left_key]=>send(opts[:left_primary_key]))
61
+ ds.exclude(opts[:right_key]=>pks).delete
62
+ pks -= ds.select_map(opts[:right_key])
63
+ pks.each{|pk| ds.insert(opts[:left_key]=>send(opts[:left_primary_key]), opts[:right_key]=>pk)}
64
+ end
65
+ end
66
+ end
67
+
68
+ # Add a getter that checks the association dataset and a setter
69
+ # that updates the associated table.
70
+ def def_one_to_many(opts)
71
+ super
72
+ return if opts[:type] == :one_to_one
73
+ def_association_pks_getter(opts) do
74
+ send(opts.dataset_method).select_map(opts[:primary_key])
75
+ end
76
+ def_association_pks_setter(opts) do |pks|
77
+ checked_transaction do
78
+ ds = send(opts.dataset_method)
79
+ ds.unfiltered.filter(opts[:primary_key]=>pks).update(opts[:key]=>pk)
80
+ ds.exclude(opts[:primary_key]=>pks).update(opts[:key]=>nil)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -5,6 +5,14 @@ module Sequel
5
5
  # method returns a dataset. This plugin makes the association method return a proxy
6
6
  # that will load the association and call a method on the association array if sent
7
7
  # an array method, and otherwise send the method to the association's dataset.
8
+ #
9
+ # Usage:
10
+ #
11
+ # # Use association proxies in all model subclasses (called before loading subclasses)
12
+ # Sequel::Model.plugin :association_proxies
13
+ #
14
+ # # Use association proxies in a specific model subclass
15
+ # Album.plugin :association_proxies
8
16
  module AssociationProxies
9
17
  # A proxy for the association. Calling an array method will load the
10
18
  # associated objects and call the method on the associated object array.
@@ -4,14 +4,20 @@ module Sequel
4
4
  # for boolean columns, which provide a nicer API. By default, the accessors
5
5
  # are created for all columns of type :boolean. However, you can provide a
6
6
  # block to the plugin to change the criteria used to determine if a
7
- # column is boolean:
7
+ # column is boolean. The block is yielded with the column symbol for each
8
+ # column in the models dataset.
8
9
  #
9
- # Sequel::Model.plugin(:boolean_readers){|c| db_schema[c][:db_type] =~ /\Atinyint/}
10
+ # Usage:
10
11
  #
11
- # This may be useful if you are using MySQL and have some tinyint columns
12
- # that represent booleans and others that represent integers. You can turn
13
- # the convert_tinyint_to_bool setting off and use the attribute methods for
14
- # the integer value and the attribute? methods for the boolean value.
12
+ # # Add boolean attribute? methods for all columns of type :boolean
13
+ # # in all model subclasses (called before loading subclasses)
14
+ # Sequel::Model.plugin :boolean_readers
15
+ #
16
+ # # Add boolean readers for all tinyint columns in the Album class
17
+ # Album.plugin(:boolean_readers){|c| db_schema[c][:db_type] =~ /\Atinyint/}
18
+ #
19
+ # # Add a boolean reader for a specific columns in the Artist class
20
+ # Artist.plugin(:boolean_readers){|c| [:column1, :column2, :column3].include?(c)}
15
21
  module BooleanReaders
16
22
  # Default proc for determining if given column is a boolean, which
17
23
  # just checks that the :type is boolean.
@@ -2,12 +2,7 @@ module Sequel
2
2
  module Plugins
3
3
  # Sequel's built-in caching plugin supports caching to any object that
4
4
  # implements the Ruby-Memcache API (or memcached API with the :ignore_exceptions
5
- # option). You can add caching for any model or for all models via:
6
- #
7
- # Model.plugin :caching, store # Cache all models
8
- # MyModel.plugin :caching, store # Just cache MyModel
9
- #
10
- # The cache store should implement the Ruby-Memcache API:
5
+ # option):
11
6
  #
12
7
  # cache_store.set(key, obj, time) # Associate the obj with the given key
13
8
  # # in the cache for the time (specified
@@ -24,6 +19,18 @@ module Sequel
24
19
  #
25
20
  # Note that only Model.[] method calls with a primary key argument are cached
26
21
  # using this plugin.
22
+ #
23
+ # Usage:
24
+ #
25
+ # # Make all subclasses use the same cache (called before loading subclasses)
26
+ # # using the Ruby-Memcache API, with the cache stored in the CACHE constant
27
+ # Sequel::Model.plugin :caching, CACHE
28
+ #
29
+ # # Make the Album class use the cache with a 30 minute time-to-live
30
+ # Album.plugin :caching, CACHE, :ttl=>1800
31
+ #
32
+ # # Make the Artist class use a cache with the memcached protocol
33
+ # Artist.plugin :caching, MEMCACHED_CACHE, :ignore_exceptions=>true
27
34
  module Caching
28
35
  # Set the cache_store and cache_ttl attributes for the given model.
29
36
  # If the :ttl option is not given, 3600 seconds is the default.
@@ -105,8 +112,8 @@ module Sequel
105
112
  module InstanceMethods
106
113
  # Remove the object from the cache when updating
107
114
  def before_update
108
- return false if super == false
109
115
  cache_delete
116
+ super
110
117
  end
111
118
 
112
119
  # Return a key unique to the underlying record for caching, based on the
@@ -5,11 +5,11 @@ module Sequel
5
5
  # unique to that model class (or subclass hierarchy) being stored in the related
6
6
  # table. For example, with this hierarchy:
7
7
  #
8
- # Employee
9
- # / \
10
- # Staff Manager
11
- # |
12
- # Executive
8
+ # Employee
9
+ # / \
10
+ # Staff Manager
11
+ # |
12
+ # Executive
13
13
  #
14
14
  # the following database schema may be used (table - columns):
15
15
  #
@@ -54,6 +54,16 @@ module Sequel
54
54
  # a = Employee.all # [<#Staff>, <#Manager>, <#Executive>]
55
55
  # a.first.values # {:id=>1, name=>'S', :kind=>'Staff'}
56
56
  # a.first.manager_id # Loads the manager_id attribute from the database
57
+ #
58
+ # Usage:
59
+ #
60
+ # # Set up class table inheritance in the parent class
61
+ # # (Not in the subclasses)
62
+ # Employee.plugin :class_table_inheritance
63
+ #
64
+ # # Set the +kind+ column to hold the class name, and
65
+ # # set the subclass table to map to for each subclass
66
+ # Employee.plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
57
67
  module ClassTableInheritance
58
68
  # The class_table_inheritance plugin requires the lazy_attributes plugin
59
69
  # to handle lazily-loaded attributes for subclass instances returned
@@ -71,10 +81,6 @@ module Sequel
71
81
  # * :table_map - Hash with class name symbol keys and table name symbol
72
82
  # values. Necessary if the implicit table name for the model class
73
83
  # does not match the database table name
74
- # Example:
75
- # class Employee < Sequel::Model
76
- # plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
77
- # end
78
84
  def self.configure(model, opts={}, &block)
79
85
  model.instance_eval do
80
86
  m = method(:constantize)
@@ -9,7 +9,8 @@ module Sequel
9
9
  # to deal with Date objects in your ruby code. This can be handled
10
10
  # with:
11
11
  #
12
- # Model.composition :date, :mapping=>[:year, :month, :day]
12
+ # Album.plugin :composition
13
+ # Album.composition :date, :mapping=>[:year, :month, :day]
13
14
  #
14
15
  # The :mapping option is optional, but you can define custom
15
16
  # composition and decomposition procs via the :composer and
@@ -10,12 +10,15 @@ module Sequel
10
10
  # can either do so in the plugin call itself, or via the
11
11
  # forced_encoding class accessor:
12
12
  #
13
- # class Album < Sequel::Model
14
- # plugin :force_encoding, 'UTF-8'
15
- # # or
16
- # plugin :force_encoding
17
- # self.forced_encoding = 'UTF-8'
18
- # end
13
+ # Usage:
14
+ #
15
+ # # Force all strings to be UTF8 encoded in a all model subclasses
16
+ # # (called before loading subclasses)
17
+ # Sequel::Model.plugin :force_encoding, 'UTF-8'
18
+ #
19
+ # # Force the encoding for the Album model to UTF8
20
+ # Album.plugin :force_encoding
21
+ # Album.forced_encoding = 'UTF-8'
19
22
  module ForceEncoding
20
23
  # Set the forced_encoding based on the value given in the plugin call.
21
24
  # Note that if a the plugin has been previously loaded, any previous
@@ -69,4 +72,4 @@ module Sequel
69
72
  end
70
73
  else
71
74
  raise LoadError, 'ForceEncoding plugin only works on Ruby 1.9+'
72
- end
75
+ end
@@ -23,6 +23,14 @@ module Sequel
23
23
  # Note that returning false in any before hook block will skip further
24
24
  # before hooks and abort the action. So if a before_save hook block returns
25
25
  # false, future before_save hook blocks are not called, and the save is aborted.
26
+ #
27
+ # Usage:
28
+ #
29
+ # # Allow use of hook class methods in all model subclasses (called before loading subclasses)
30
+ # Sequel::Model.plugin :hook_class_methods
31
+ #
32
+ # # Allow the use of hook class methods in the Album class
33
+ # Album.plugin :hook_class_methods
26
34
  module HookClassMethods
27
35
  # Set up the hooks instance variable in the model.
28
36
  def self.apply(model)
@@ -44,7 +52,7 @@ module Sequel
44
52
  # the symbol specifies an instance method to call and adds it to the hook
45
53
  # type.
46
54
  #
47
- # If any hook block returns false, the instance method will return false
55
+ # If any before hook block returns false, the instance method will return false
48
56
  # immediately without running the rest of the hooks of that type.
49
57
  #
50
58
  # It is recommended that you always provide a symbol to this method,
@@ -66,7 +74,7 @@ module Sequel
66
74
  hooks.each do |hook|
67
75
  @hooks[hook] = []
68
76
  instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__)
69
- class_eval("def #{hook}; run_hooks(:#{hook}); end", __FILE__, __LINE__)
77
+ class_eval("def #{hook}; model.hook_blocks(:#{hook}){|b| return false if instance_eval(&b) == false}; end", __FILE__, __LINE__)
70
78
  end
71
79
  end
72
80
 
@@ -111,15 +119,8 @@ module Sequel
111
119
  end
112
120
 
113
121
  module InstanceMethods
114
- Model::HOOKS.each{|h| class_eval("def #{h}; return false if super == false; run_hooks(:#{h}); end", __FILE__, __LINE__)}
115
-
116
- private
117
-
118
- # Runs all hook blocks of given hook type on this object.
119
- # Stops running hook blocks and returns false if any hook block returns false.
120
- def run_hooks(hook)
121
- model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
122
- end
122
+ Model::BEFORE_HOOKS.each{|h| class_eval("def #{h}; model.hook_blocks(:#{h}){|b| return false if instance_eval(&b) == false}; super; end", __FILE__, __LINE__)}
123
+ Model::AFTER_HOOKS.each{|h| class_eval("def #{h}; super; model.hook_blocks(:#{h}){|b| instance_eval(&b)}; end", __FILE__, __LINE__)}
123
124
  end
124
125
  end
125
126
  end
@@ -21,6 +21,15 @@ module Sequel
21
21
  #
22
22
  # Identity maps are thread-local and only presist for the duration of the block,
23
23
  # so they should be should only be considered as a possible performance enhancer.
24
+ #
25
+ # Usage:
26
+ #
27
+ # # Use an identity map that will affect all model classes (called before loading subclasses)
28
+ # Sequel::Model.plugin :identity_map
29
+ #
30
+ # # Use an identity map just for the Album class
31
+ # Album.plugin :identity_map
32
+ # # would need to do Album.with_identity_map{} to use the identity map
24
33
  module IdentityMap
25
34
  module ClassMethods
26
35
  # Returns the current thread-local identity map. Should be a hash if
@@ -11,19 +11,28 @@ module Sequel
11
11
  # false, no more instance level before hooks are called and false is returned.
12
12
  #
13
13
  # Instance level hooks are cleared when the object is saved successfully.
14
+ #
15
+ # Usage:
16
+ #
17
+ # # Add the instance hook methods to all model subclass instances (called before loading subclasses)
18
+ # Sequel::Model.plugin :instance_hooks
19
+ #
20
+ # # Add the instance hook methods just to Album instances
21
+ # Album.plugin :instance_hooks
14
22
  module InstanceHooks
15
23
  module InstanceMethods
16
- HOOKS = Sequel::Model::HOOKS - [:after_initialize]
24
+ BEFORE_HOOKS = Sequel::Model::BEFORE_HOOKS
25
+ AFTER_HOOKS = Sequel::Model::AFTER_HOOKS - [:after_initialize]
26
+ HOOKS = BEFORE_HOOKS + AFTER_HOOKS
17
27
  HOOKS.each{|h| class_eval("def #{h}_hook(&block); add_instance_hook(:#{h}, &block) end", __FILE__, __LINE__)}
18
28
 
19
- BEFORE_HOOKS, AFTER_HOOKS = HOOKS.partition{|hook| hook.to_s =~ /\Abefore_/}
20
- BEFORE_HOOKS.each{|h| class_eval("def #{h}; run_instance_hooks(:#{h}) == false ? false : super end", __FILE__, __LINE__)}
21
- AFTER_HOOKS.each{|h| class_eval("def #{h}; super; run_instance_hooks(:#{h}) end", __FILE__, __LINE__)}
29
+ BEFORE_HOOKS.each{|h| class_eval("def #{h}; run_before_instance_hooks(:#{h}) == false ? false : super end", __FILE__, __LINE__)}
30
+ (AFTER_HOOKS - [:after_save]).each{|h| class_eval("def #{h}; super; run_after_instance_hooks(:#{h}) end", __FILE__, __LINE__)}
22
31
 
23
32
  # Clear the instance level hooks after saving the object.
24
33
  def after_save
25
34
  super
26
- run_instance_hooks(:after_save)
35
+ run_after_instance_hooks(:after_save)
27
36
  @instance_hooks.clear if @instance_hooks
28
37
  end
29
38
 
@@ -42,14 +51,15 @@ module Sequel
42
51
  @instance_hooks[hook] ||= []
43
52
  end
44
53
 
45
- # Run all hook blocks of the given hook type. If a before hook,
46
- # immediately return false if any hook block call returns false.
47
- def run_instance_hooks(hook)
48
- if BEFORE_HOOKS.include?(hook)
49
- instance_hooks(hook).each{|b| return false if b.call == false}
50
- else
51
- instance_hooks(hook).each{|b| b.call}
52
- end
54
+ # Run all hook blocks of the given hook type.
55
+ def run_after_instance_hooks(hook)
56
+ instance_hooks(hook).each{|b| b.call}
57
+ end
58
+
59
+ # Run all hook blocks of the given hook type. If a hook block returns false,
60
+ # immediately return false without running the remaining blocks.
61
+ def run_before_instance_hooks(hook)
62
+ instance_hooks(hook).each{|b| return false if b.call == false}
53
63
  end
54
64
  end
55
65
  end
@@ -16,6 +16,9 @@ module Sequel
16
16
  # a.review
17
17
  # end
18
18
  # end
19
+ #
20
+ # # You can specify multiple columns to lazily load:
21
+ # Album.plugin :lazy_attributes, :review, :tracklist
19
22
  module LazyAttributes
20
23
  # Lazy attributes requires the identity map and tactical eager loading plugins
21
24
  def self.apply(model, *attrs)
@@ -67,7 +70,7 @@ module Sequel
67
70
  # the attribute for the current object.
68
71
  def lazy_attribute_lookup(a)
69
72
  primary_key = model.primary_key
70
- model.select(*(Array(primary_key) + [a])).filter(primary_key=>retrieved_with.map{|o| o.pk}.sql_array).all if model.identity_map && retrieved_with
73
+ model.select(*(Array(primary_key) + [a])).filter(primary_key=>::Sequel::SQL::SQLArray.new(retrieved_with.map{|o| o.pk})).all if model.identity_map && retrieved_with
71
74
  values[a] = this.select(a).first[a] unless values.include?(a)
72
75
  values[a]
73
76
  end
@@ -8,6 +8,7 @@ module Sequel
8
8
  #
9
9
  # The many_through_many plugin would allow this:
10
10
  #
11
+ # Artist.plugin :many_through_many
11
12
  # Artist.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
12
13
  #
13
14
  # Which will give you the tags for all of the artist's albums.
@@ -28,6 +29,19 @@ module Sequel
28
29
  # # All tracks on albums by this artist
29
30
  # Artist.many_through_many :tracks, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id]], \
30
31
  # :right_primary_key=>:album_id
32
+ #
33
+ # Often you don't want the current object to appear in the array of associated objects. This is easiest to handle via an :after_load hook:
34
+ #
35
+ # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]],
36
+ # :after_load=>proc{|artist, associated_artists| associated_artists.delete(artist)}
37
+ #
38
+ # You can also handle it by adding a dataset block that excludes the current record (so it won't be retrieved at all), but
39
+ # that won't work when eagerly loading, which is why the :after_load proc is recommended instead.
40
+ #
41
+ # It's also common to not want duplicate records, in which case the :distinct option can be used:
42
+ #
43
+ # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]],
44
+ # :distinct=>true
31
45
  module ManyThroughMany
32
46
  # The AssociationReflection subclass for many_through_many associations.
33
47
  class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
@@ -165,15 +179,15 @@ module Sequel
165
179
  end
166
180
 
167
181
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
168
- opts[:eager_loader] ||= lambda do |key_hash, records, associations|
169
- h = key_hash[left_pk]
170
- records.each{|object| object.associations[name] = []}
182
+ opts[:eager_loader] ||= lambda do |eo|
183
+ h = eo[:key_hash][left_pk]
184
+ eo[:rows].each{|object| object.associations[name] = []}
171
185
  ds = opts.associated_class
172
186
  opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
173
187
  ft = opts[:final_reverse_edge]
174
188
  conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, SQL::SQLArray.new(h.keys)]] : [[left_key, h.keys]]
175
189
  ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
176
- model.eager_loading_dataset(opts, ds, Array(opts.select), associations).all do |assoc_record|
190
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
177
191
  hash_key = if uses_lcks
178
192
  left_key_alias.map{|k| assoc_record.values.delete(k)}
179
193
  else