sequel 3.11.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
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