epugh-sequel 0.0.0

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