epugh-sequel 0.0.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 (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,74 @@
1
+ module Sequel
2
+ # Empty namespace that plugins should use to store themselves,
3
+ # so they can be loaded via Model.plugin.
4
+ #
5
+ # Plugins should be modules with one of the following conditions:
6
+ # * A singleton method named apply, which takes a model and
7
+ # additional arguments.
8
+ # * A module inside the plugin module named InstanceMethods,
9
+ # which will be included in the model class.
10
+ # * A module inside the plugin module named ClassMethods,
11
+ # which will extend the model class.
12
+ # * A module inside the plugin module named DatasetMethods,
13
+ # which will extend the model's dataset.
14
+ module Plugins
15
+ end
16
+
17
+ class Model
18
+ # Loads a plugin for use with the model class, passing optional arguments
19
+ # to the plugin. If the plugin has a DatasetMethods module and the model
20
+ # doesn't have a dataset, raise an Error.
21
+ def self.plugin(plugin, *args)
22
+ arg = args.first
23
+ block = lambda{arg}
24
+ m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
25
+ if m.respond_to?(:apply)
26
+ m.apply(self, *args)
27
+ end
28
+ if m.const_defined?("InstanceMethods")
29
+ define_method(:"#{plugin}_opts", &block)
30
+ include(m::InstanceMethods)
31
+ end
32
+ if m.const_defined?("ClassMethods")
33
+ meta_def(:"#{plugin}_opts", &block)
34
+ extend(m::ClassMethods)
35
+ end
36
+ if m.const_defined?("DatasetMethods")
37
+ if @dataset
38
+ dataset.meta_def(:"#{plugin}_opts", &block)
39
+ dataset.extend(m::DatasetMethods)
40
+ end
41
+ dataset_method_modules << m::DatasetMethods
42
+ def_dataset_method(*m::DatasetMethods.public_instance_methods.reject{|x| DATASET_METHOD_RE !~ x.to_s})
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ private
48
+
49
+ # Returns the new style location for the plugin name.
50
+ def plugin_gem_location(plugin)
51
+ "sequel/plugins/#{plugin}"
52
+ end
53
+
54
+ # Returns the old style location for the plugin name.
55
+ def plugin_gem_location_old(plugin)
56
+ "sequel_#{plugin}"
57
+ end
58
+
59
+ # Returns the module for the specified plugin. If the module is not
60
+ # defined, the corresponding plugin gem is automatically loaded.
61
+ def plugin_module(plugin)
62
+ module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
63
+ if not Sequel::Plugins.const_defined?(module_name)
64
+ begin
65
+ require plugin_gem_location(plugin)
66
+ rescue LoadError
67
+ require plugin_gem_location_old(plugin)
68
+ end
69
+ end
70
+ Sequel::Plugins.const_get(module_name)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,230 @@
1
+ module Sequel
2
+ class Dataset
3
+ # Allows you to join multiple datasets/tables and have the result set
4
+ # split into component tables.
5
+ #
6
+ # This differs from the usual usage of join, which returns the result set
7
+ # as a single hash. For example:
8
+ #
9
+ # # CREATE TABLE artists (id INTEGER, name TEXT);
10
+ # # CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
11
+ # DB[:artists].left_outer_join(:albums, :artist_id=>:id).first
12
+ # => {:id=>(albums.id||artists.id), :name=>(albums.name||artist.names), :artist_id=>albums.artist_id}
13
+ # DB[:artists].graph(:albums, :artist_id=>:id).first
14
+ # => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>{:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}}
15
+ #
16
+ # Using a join such as left_outer_join, the attribute names that are shared between
17
+ # the tables are combined in the single return hash. You can get around that by
18
+ # using .select with correct aliases for all of the columns, but it is simpler to
19
+ # use graph and have the result set split for you. In addition, graph respects
20
+ # any row_proc or transform attributes of the current dataset and the datasets
21
+ # you use with graph.
22
+ #
23
+ # If you are graphing a table and all columns for that table are nil, this
24
+ # indicates that no matching rows existed in the table, so graph will return nil
25
+ # instead of a hash with all nil values:
26
+ #
27
+ # # If the artist doesn't have any albums
28
+ # DB[:artists].graph(:albums, :artist_id=>:id).first
29
+ # => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>nil}
30
+ #
31
+ # Arguments:
32
+ # * dataset - Can be a symbol (specifying a table), another dataset,
33
+ # or an object that responds to .dataset and yields a symbol or a dataset
34
+ # * join_conditions - Any condition(s) allowed by join_table.
35
+ # * options - A hash of graph options. The following options are currently used:
36
+ # * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
37
+ # * :join_type - The type of join to use (passed to join_table). Defaults to
38
+ # :left_outer.
39
+ # * :select - An array of columns to select. When not used, selects
40
+ # all columns in the given dataset. When set to false, selects no
41
+ # columns and is like simply joining the tables, though graph keeps
42
+ # some metadata about join that makes it important to use graph instead
43
+ # of join.
44
+ # * :table_alias - The alias to use for the table. If not specified, doesn't
45
+ # alias the table. You will get an error if the the alias (or table) name is
46
+ # used more than once.
47
+ # * block - A block that is passed to join_table.
48
+ def graph(dataset, join_conditions = nil, options = {}, &block)
49
+ # Allow the use of a model, dataset, or symbol as the first argument
50
+ # Find the table name/dataset based on the argument
51
+ dataset = dataset.dataset if dataset.respond_to?(:dataset)
52
+ case dataset
53
+ when Symbol
54
+ table = dataset
55
+ dataset = @db[dataset]
56
+ when ::Sequel::Dataset
57
+ table = dataset.first_source
58
+ else
59
+ raise Error, "The dataset argument should be a symbol, dataset, or model"
60
+ end
61
+
62
+ # Raise Sequel::Error with explanation that the table alias has been used
63
+ raise_alias_error = lambda do
64
+ raise(Error, "this #{options[:table_alias] ? 'alias' : 'table'} has already been been used, please specify " \
65
+ "#{options[:table_alias] ? 'a different alias' : 'an alias via the :table_alias option'}")
66
+ end
67
+
68
+ # Only allow table aliases that haven't been used
69
+ table_alias = options[:table_alias] || table
70
+ raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
71
+
72
+ # Join the table early in order to avoid cloning the dataset twice
73
+ ds = join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
74
+ opts = ds.opts
75
+
76
+ # Whether to include the table in the result set
77
+ add_table = options[:select] == false ? false : true
78
+ # Whether to add the columns to the list of column aliases
79
+ add_columns = !ds.opts.include?(:graph_aliases)
80
+
81
+ # Setup the initial graph data structure if it doesn't exist
82
+ unless graph = opts[:graph]
83
+ master = ds.first_source
84
+ raise_alias_error.call if master == table_alias
85
+ # Master hash storing all .graph related information
86
+ graph = opts[:graph] = {}
87
+ # Associates column aliases back to tables and columns
88
+ column_aliases = graph[:column_aliases] = {}
89
+ # Associates table alias (the master is never aliased)
90
+ table_aliases = graph[:table_aliases] = {master=>self}
91
+ # Keep track of the alias numbers used
92
+ ca_num = graph[:column_alias_num] = Hash.new(0)
93
+ # All columns in the master table are never
94
+ # aliased, but are not included if set_graph_aliases
95
+ # has been used.
96
+ if add_columns
97
+ select = opts[:select] = []
98
+ columns.each do |column|
99
+ column_aliases[column] = [master, column]
100
+ select.push(column.qualify(master))
101
+ end
102
+ end
103
+ end
104
+
105
+ # Add the table alias to the list of aliases
106
+ # Even if it isn't been used in the result set,
107
+ # we add a key for it with a nil value so we can check if it
108
+ # is used more than once
109
+ table_aliases = graph[:table_aliases]
110
+ table_aliases[table_alias] = add_table ? dataset : nil
111
+
112
+ # Add the columns to the selection unless we are ignoring them
113
+ if add_table && add_columns
114
+ select = opts[:select]
115
+ column_aliases = graph[:column_aliases]
116
+ ca_num = graph[:column_alias_num]
117
+ # Which columns to add to the result set
118
+ cols = options[:select] || dataset.columns
119
+ # If the column hasn't been used yet, don't alias it.
120
+ # If it has been used, try table_column.
121
+ # If that has been used, try table_column_N
122
+ # using the next value of N that we know hasn't been
123
+ # used
124
+ cols.each do |column|
125
+ col_alias, identifier = if column_aliases[column]
126
+ column_alias = :"#{table_alias}_#{column}"
127
+ if column_aliases[column_alias]
128
+ column_alias_num = ca_num[column_alias]
129
+ column_alias = :"#{column_alias}_#{column_alias_num}"
130
+ ca_num[column_alias] += 1
131
+ end
132
+ [column_alias, column.qualify(table_alias).as(column_alias)]
133
+ else
134
+ [column, column.qualify(table_alias)]
135
+ end
136
+ column_aliases[col_alias] = [table_alias, column]
137
+ select.push(identifier)
138
+ end
139
+ end
140
+ ds
141
+ end
142
+
143
+ # This allows you to manually specify the graph aliases to use
144
+ # when using graph. You can use it to only select certain
145
+ # columns, and have those columns mapped to specific aliases
146
+ # in the result set. This is the equivalent of .select for a
147
+ # graphed dataset, and must be used instead of .select whenever
148
+ # graphing is used. Example:
149
+ #
150
+ # DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name]).first
151
+ # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name}}
152
+ #
153
+ # Arguments:
154
+ # * graph_aliases - Should be a hash with keys being symbols of
155
+ # column aliases, and values being arrays with two symbol elements.
156
+ # The first element of the array should be the table alias,
157
+ # and the second should be the actual column name.
158
+ def set_graph_aliases(graph_aliases)
159
+ ds = select(*graph_alias_columns(graph_aliases))
160
+ ds.opts[:graph_aliases] = graph_aliases
161
+ ds
162
+ end
163
+
164
+ # Adds the give graph aliases to the list of graph aliases to use,
165
+ # unlike #set_graph_aliases, which replaces the list. See
166
+ # #set_graph_aliases.
167
+ def add_graph_aliases(graph_aliases)
168
+ ds = select_more(*graph_alias_columns(graph_aliases))
169
+ ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || {}).merge(graph_aliases)
170
+ ds
171
+ end
172
+
173
+ private
174
+
175
+ # Transform the hash of graph aliases to an array of columns
176
+ def graph_alias_columns(graph_aliases)
177
+ graph_aliases.collect do |col_alias, tc|
178
+ identifier = tc[2] || tc[1].qualify(tc[0])
179
+ identifier = SQL::AliasedExpression.new(identifier, col_alias) if tc[2] or tc[1] != col_alias
180
+ identifier
181
+ end
182
+ end
183
+
184
+ # Fetch the rows, split them into component table parts,
185
+ # tranform and run the row_proc on each part (if applicable),
186
+ # and yield a hash of the parts.
187
+ def graph_each(opts=(defarg=true;nil), &block)
188
+ # Reject tables with nil datasets, as they are excluded from
189
+ # the result set
190
+ datasets = @opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
191
+ # Get just the list of table aliases into a local variable, for speed
192
+ table_aliases = datasets.collect{|ta,ds| ta}
193
+ # Get an array of arrays, one for each dataset, with
194
+ # the necessary information about each dataset, for speed
195
+ datasets = datasets.collect do |ta, ds|
196
+ [ta, ds, ds.instance_variable_get(:@transform), ds.row_proc]
197
+ end
198
+ # Use the manually set graph aliases, if any, otherwise
199
+ # use the ones automatically created by .graph
200
+ column_aliases = @opts[:graph_aliases] || @opts[:graph][:column_aliases]
201
+ fetch_rows(defarg ? select_sql : select_sql(opts)) do |r|
202
+ graph = {}
203
+ # Create the sub hashes, one per table
204
+ table_aliases.each{|ta| graph[ta]={}}
205
+ # Split the result set based on the column aliases
206
+ # If there are columns in the result set that are
207
+ # not in column_aliases, they are ignored
208
+ column_aliases.each do |col_alias, tc|
209
+ ta, column = tc
210
+ graph[ta][column] = r[col_alias]
211
+ end
212
+ # For each dataset, transform and run the row
213
+ # row_proc if applicable
214
+ datasets.each do |ta,ds,tr,rp|
215
+ g = graph[ta]
216
+ graph[ta] = if g.values.any?{|x| !x.nil?}
217
+ g = ds.transform_load(g) if tr
218
+ g = rp[g] if rp
219
+ g
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ yield graph
226
+ end
227
+ self
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,122 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built-in caching plugin supports caching to any object that
4
+ # implements the Ruby-Memcache API. You can add caching for any model
5
+ # 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:
11
+ #
12
+ # cache_store.set(key, obj, time) # Associate the obj with the given key
13
+ # # in the cache for the time (specified
14
+ # # in seconds)
15
+ # cache_store.get(key) => obj # Returns object set with same key
16
+ # cache_store.get(key2) => nil # nil returned if there isn't an object
17
+ # # currently in the cache with that key
18
+ module Caching
19
+ # Set the cache_store and cache_ttl attributes for the given model.
20
+ # If the :ttl option is not given, 3600 seconds is the default.
21
+ def self.apply(model, store, opts={})
22
+ model.instance_eval do
23
+ @cache_store = store
24
+ @cache_ttl = opts[:ttl] || 3600
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ # The cache store object for the model, which should implement the
30
+ # Ruby-Memcache API
31
+ attr_reader :cache_store
32
+
33
+ # The time to live for the cache store, in seconds.
34
+ attr_reader :cache_ttl
35
+
36
+ # Check the cache before a database lookup unless a hash is supplied.
37
+ def [](*args)
38
+ args = args.first if (args.size == 1)
39
+ return super(args) if args.is_a?(Hash)
40
+ ck = cache_key(args)
41
+ if obj = @cache_store.get(ck)
42
+ return obj
43
+ end
44
+ if obj = super(args)
45
+ @cache_store.set(ck, obj, @cache_ttl)
46
+ end
47
+ obj
48
+ end
49
+
50
+ # Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
51
+ def set_cache_ttl(ttl)
52
+ @cache_ttl = ttl
53
+ end
54
+
55
+ # Copy the cache_store and cache_ttl to the subclass.
56
+ def inherited(subclass)
57
+ super
58
+ store = @cache_store
59
+ ttl = @cache_ttl
60
+ subclass.instance_eval do
61
+ @cache_store = store
62
+ @cache_ttl = ttl
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # Delete the entry with the matching key from the cache
69
+ def cache_delete(key)
70
+ @cache_store.delete(key)
71
+ nil
72
+ end
73
+
74
+ # Return a key string for the pk
75
+ def cache_key(pk)
76
+ "#{self}:#{Array(pk).join(',')}"
77
+ end
78
+ end
79
+
80
+ module InstanceMethods
81
+ # Remove the object from the cache when updating
82
+ def before_update
83
+ return false if super == false
84
+ cache_delete
85
+ end
86
+
87
+ # Return a key unique to the underlying record for caching, based on the
88
+ # primary key value(s) for the object. If the model does not have a primary
89
+ # key, raise an Error.
90
+ def cache_key
91
+ raise(Error, "No primary key is associated with this model") unless key = primary_key
92
+ pk = case key
93
+ when Array
94
+ key.collect{|k| @values[k]}
95
+ else
96
+ @values[key] || (raise Error, 'no primary key for this record')
97
+ end
98
+ model.send(:cache_key, pk)
99
+ end
100
+
101
+ # Remove the object from the cache when deleting
102
+ def delete
103
+ cache_delete
104
+ super
105
+ end
106
+
107
+ # Remove the object from the cache when updating
108
+ def update_values(*args)
109
+ cache_delete
110
+ super
111
+ end
112
+
113
+ private
114
+
115
+ # Delete this object from the cache
116
+ def cache_delete
117
+ model.send(:cache_delete, cache_key)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,122 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Sequel's built-in hook class methods plugin is designed for backwards
4
+ # compatibility. Its use is not encouraged, it is recommended to use
5
+ # instance methods and super instead of this plugin. What this plugin
6
+ # allows you to do is, for example:
7
+ #
8
+ # # Block only, can cause duplicate hooks if code is reloaded
9
+ # before_save{self.created_at = Time.now}
10
+ # # Block with tag, safe for reloading
11
+ # before_save(:set_created_at){self.created_at = Time.now}
12
+ # # Tag only, safe for reloading, calls instance method
13
+ # before_save(:set_created_at)
14
+ #
15
+ # Pretty much anything you can do with a hook class method, you can also
16
+ # do with an instance method instead:
17
+ #
18
+ # def before_save
19
+ # return false if super == false
20
+ # self.created_at = Time.now
21
+ # end
22
+ module HookClassMethods
23
+ # Set up the hooks instance variable in the model.
24
+ def self.apply(model)
25
+ hooks = model.instance_variable_set(:@hooks, {})
26
+ Model::HOOKS.each{|h| hooks[h] = []}
27
+ end
28
+
29
+ module ClassMethods
30
+ Model::HOOKS.each{|h| class_eval("def #{h}(method = nil, &block); add_hook(:#{h}, method, &block) end", __FILE__, __LINE__)}
31
+
32
+ # This adds a new hook type. It will define both a class
33
+ # method that you can use to add hooks, as well as an instance method
34
+ # that you can use to call all hooks of that type. The class method
35
+ # can be called with a symbol or a block or both. If a block is given and
36
+ # and symbol is not, it adds the hook block to the hook type. If a block
37
+ # and symbol are both given, it replaces the hook block associated with
38
+ # that symbol for a given hook type, or adds it if there is no hook block
39
+ # with that symbol for that hook type. If no block is given, it assumes
40
+ # the symbol specifies an instance method to call and adds it to the hook
41
+ # type.
42
+ #
43
+ # If any hook block returns false, the instance method will return false
44
+ # immediately without running the rest of the hooks of that type.
45
+ #
46
+ # It is recommended that you always provide a symbol to this method,
47
+ # for descriptive purposes. It's only necessary to do so when you
48
+ # are using a system that reloads code.
49
+ #
50
+ # Example of usage:
51
+ #
52
+ # class MyModel
53
+ # define_hook :before_move_to
54
+ # before_move_to(:check_move_allowed){|o| o.allow_move?}
55
+ # def move_to(there)
56
+ # return if before_move_to == false
57
+ # # move MyModel object to there
58
+ # end
59
+ # end
60
+ def add_hook_type(*hooks)
61
+ Model::HOOKS.concat(hooks)
62
+ hooks.each do |hook|
63
+ @hooks[hook] = []
64
+ instance_eval("def #{hook}(method = nil, &block); add_hook(:#{hook}, method, &block) end", __FILE__, __LINE__)
65
+ class_eval("def #{hook}; run_hooks(:#{hook}); end", __FILE__, __LINE__)
66
+ end
67
+ end
68
+
69
+ # Returns true if there are any hook blocks for the given hook.
70
+ def has_hooks?(hook)
71
+ !@hooks[hook].empty?
72
+ end
73
+
74
+ # Yield every block related to the given hook.
75
+ def hook_blocks(hook)
76
+ @hooks[hook].each{|k,v| yield v}
77
+ end
78
+
79
+ # Make a copy of the current class's hooks for the subclass.
80
+ def inherited(subclass)
81
+ super
82
+ hooks = subclass.instance_variable_set(:@hooks, {})
83
+ instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
84
+ end
85
+
86
+ private
87
+
88
+ # Add a hook block to the list of hook methods.
89
+ # If a non-nil tag is given and it already is in the list of hooks,
90
+ # replace it with the new block.
91
+ def add_hook(hook, tag, &block)
92
+ unless block
93
+ (raise Error, 'No hook method specified') unless tag
94
+ block = proc {send tag}
95
+ end
96
+ h = @hooks[hook]
97
+ if tag && (old = h.find{|x| x[0] == tag})
98
+ old[1] = block
99
+ else
100
+ if hook.to_s =~ /^before/
101
+ h.unshift([tag,block])
102
+ else
103
+ h << [tag, block]
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ module InstanceMethods
110
+ Model::HOOKS.each{|h| class_eval("def #{h}; run_hooks(:#{h}); end", __FILE__, __LINE__)}
111
+
112
+ private
113
+
114
+ # Runs all hook blocks of given hook type on this object.
115
+ # Stops running hook blocks and returns false if any hook block returns false.
116
+ def run_hooks(hook)
117
+ model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end