sequel_core 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/CHANGELOG +116 -0
  2. data/COPYING +19 -19
  3. data/README +83 -32
  4. data/Rakefile +9 -20
  5. data/bin/sequel +43 -112
  6. data/doc/cheat_sheet.rdoc +225 -0
  7. data/doc/dataset_filtering.rdoc +257 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
  9. data/lib/sequel_core/adapters/ado.rb +3 -1
  10. data/lib/sequel_core/adapters/db2.rb +4 -2
  11. data/lib/sequel_core/adapters/dbi.rb +127 -113
  12. data/lib/sequel_core/adapters/informix.rb +4 -2
  13. data/lib/sequel_core/adapters/jdbc.rb +5 -3
  14. data/lib/sequel_core/adapters/mysql.rb +112 -46
  15. data/lib/sequel_core/adapters/odbc.rb +5 -7
  16. data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
  17. data/lib/sequel_core/adapters/openbase.rb +3 -1
  18. data/lib/sequel_core/adapters/oracle.rb +11 -9
  19. data/lib/sequel_core/adapters/postgres.rb +261 -262
  20. data/lib/sequel_core/adapters/sqlite.rb +72 -22
  21. data/lib/sequel_core/connection_pool.rb +140 -73
  22. data/lib/sequel_core/core_ext.rb +201 -66
  23. data/lib/sequel_core/core_sql.rb +123 -153
  24. data/lib/sequel_core/database/schema.rb +156 -0
  25. data/lib/sequel_core/database.rb +321 -338
  26. data/lib/sequel_core/dataset/callback.rb +11 -12
  27. data/lib/sequel_core/dataset/convenience.rb +213 -240
  28. data/lib/sequel_core/dataset/pagination.rb +58 -43
  29. data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
  30. data/lib/sequel_core/dataset/query.rb +41 -0
  31. data/lib/sequel_core/dataset/schema.rb +15 -0
  32. data/lib/sequel_core/dataset/sequelizer.rb +41 -373
  33. data/lib/sequel_core/dataset/sql.rb +741 -632
  34. data/lib/sequel_core/dataset.rb +183 -168
  35. data/lib/sequel_core/deprecated.rb +1 -169
  36. data/lib/sequel_core/exceptions.rb +24 -19
  37. data/lib/sequel_core/migration.rb +44 -52
  38. data/lib/sequel_core/object_graph.rb +43 -42
  39. data/lib/sequel_core/pretty_table.rb +71 -76
  40. data/lib/sequel_core/schema/generator.rb +163 -105
  41. data/lib/sequel_core/schema/sql.rb +250 -93
  42. data/lib/sequel_core/schema.rb +2 -8
  43. data/lib/sequel_core/sql.rb +394 -0
  44. data/lib/sequel_core/worker.rb +37 -27
  45. data/lib/sequel_core.rb +99 -45
  46. data/spec/adapters/informix_spec.rb +0 -1
  47. data/spec/adapters/mysql_spec.rb +177 -124
  48. data/spec/adapters/oracle_spec.rb +0 -1
  49. data/spec/adapters/postgres_spec.rb +98 -58
  50. data/spec/adapters/sqlite_spec.rb +45 -4
  51. data/spec/blockless_filters_spec.rb +269 -0
  52. data/spec/connection_pool_spec.rb +21 -18
  53. data/spec/core_ext_spec.rb +169 -19
  54. data/spec/core_sql_spec.rb +56 -49
  55. data/spec/database_spec.rb +78 -17
  56. data/spec/dataset_spec.rb +300 -428
  57. data/spec/migration_spec.rb +1 -1
  58. data/spec/object_graph_spec.rb +5 -11
  59. data/spec/rcov.opts +1 -1
  60. data/spec/schema_generator_spec.rb +16 -4
  61. data/spec/schema_spec.rb +89 -10
  62. data/spec/sequelizer_spec.rb +56 -56
  63. data/spec/spec.opts +0 -5
  64. data/spec/spec_config.rb +7 -0
  65. data/spec/spec_config.rb.example +5 -5
  66. data/spec/spec_helper.rb +6 -0
  67. data/spec/worker_spec.rb +1 -1
  68. metadata +78 -63
@@ -10,9 +10,7 @@ module Sequel
10
10
  end
11
11
 
12
12
  def connect
13
- if @opts[:database].nil? || @opts[:database].empty?
14
- @opts[:database] = ':memory:'
15
- end
13
+ @opts[:database] = ':memory:' if @opts[:database].blank?
16
14
  db = ::SQLite3::Database.new(@opts[:database])
17
15
  db.busy_timeout(@opts.fetch(:timeout, 5000))
18
16
  db.type_translation = true
@@ -38,31 +36,39 @@ module Sequel
38
36
  end
39
37
 
40
38
  def execute(sql)
41
- @logger.info(sql) if @logger
42
- @pool.hold {|conn| conn.execute_batch(sql); conn.changes}
43
- rescue RuntimeError => e
44
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
39
+ begin
40
+ log_info(sql)
41
+ @pool.hold {|conn| conn.execute_batch(sql); conn.changes}
42
+ rescue SQLite3::Exception => e
43
+ raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
44
+ end
45
45
  end
46
46
 
47
47
  def execute_insert(sql)
48
- @logger.info(sql) if @logger
49
- @pool.hold {|conn| conn.execute(sql); conn.last_insert_row_id}
50
- rescue RuntimeError => e
51
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
48
+ begin
49
+ log_info(sql)
50
+ @pool.hold {|conn| conn.execute(sql); conn.last_insert_row_id}
51
+ rescue SQLite3::Exception => e
52
+ raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
53
+ end
52
54
  end
53
55
 
54
56
  def single_value(sql)
55
- @logger.info(sql) if @logger
56
- @pool.hold {|conn| conn.get_first_value(sql)}
57
- rescue RuntimeError => e
58
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
57
+ begin
58
+ log_info(sql)
59
+ @pool.hold {|conn| conn.get_first_value(sql)}
60
+ rescue SQLite3::Exception => e
61
+ raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
62
+ end
59
63
  end
60
64
 
61
65
  def execute_select(sql, &block)
62
- @logger.info(sql) if @logger
63
- @pool.hold {|conn| conn.query(sql, &block)}
64
- rescue RuntimeError => e
65
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
66
+ begin
67
+ log_info(sql)
68
+ @pool.hold {|conn| conn.query(sql, &block)}
69
+ rescue SQLite3::Exception => e
70
+ raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
71
+ end
66
72
  end
67
73
 
68
74
  def pragma_get(name)
@@ -124,20 +130,64 @@ module Sequel
124
130
  result = nil
125
131
  conn.transaction {result = yield(conn)}
126
132
  result
127
- rescue => e
128
- raise e unless Error::Rollback === e
133
+ rescue ::Exception => e
134
+ raise (SQLite3::Exception === e ? Error.new(e.message) : e) unless Error::Rollback === e
135
+ end
136
+ end
137
+ end
138
+
139
+ private
140
+ def connection_pool_default_options
141
+ o = super.merge(:pool_reuse_connections=>:always, :pool_convert_exceptions=>false)
142
+ # Default to only a single connection if a memory database is used,
143
+ # because otherwise each connection will get a separate database
144
+ o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
145
+ o
146
+ end
147
+
148
+ SCHEMA_TYPE_RE = /\A(\w+)\((\d+)\)\z/
149
+ def schema_parse_table(table_name, opts)
150
+ rows = self["PRAGMA table_info('#{::SQLite3::Database.quote(table_name.to_s)}')"].collect do |row|
151
+ row.delete(:cid)
152
+ row[:column] = row.delete(:name)
153
+ row[:allow_null] = row.delete(:notnull).to_i == 0 ? 'YES' : 'NO'
154
+ row[:default] = row.delete(:dflt_value)
155
+ row[:primary_key] = row.delete(:pk).to_i == 1 ? true : false
156
+ row[:db_type] = row.delete(:type)
157
+ if m = SCHEMA_TYPE_RE.match(row[:db_type])
158
+ row[:db_type] = m[1]
159
+ row[:max_chars] = m[2].to_i
160
+ else
161
+ row[:max_chars] = nil
129
162
  end
163
+ row[:numeric_precision] = nil
164
+ row
130
165
  end
166
+ schema_parse_rows(rows)
167
+ end
168
+
169
+ def schema_parse_tables(opts)
170
+ schemas = {}
171
+ tables.each{|table| schemas[table] = schema_parse_table(table, opts)}
172
+ schemas
131
173
  end
132
174
  end
133
175
 
134
176
  class Dataset < Sequel::Dataset
135
- def quote_column_ref(c); "`#{c}`"; end
177
+ def quoted_identifier(c)
178
+ "`#{c}`"
179
+ end
136
180
 
137
181
  def literal(v)
138
182
  case v
183
+ when LiteralString
184
+ v
185
+ when String
186
+ "'#{::SQLite3::Database.quote(v)}'"
139
187
  when Time
140
188
  literal(v.iso8601)
189
+ when Date, DateTime
190
+ literal(v.to_s)
141
191
  else
142
192
  super
143
193
  end
@@ -1,70 +1,118 @@
1
- require 'thread'
2
-
3
1
  # A ConnectionPool manages access to database connections by keeping
4
2
  # multiple connections and giving threads exclusive access to each
5
3
  # connection.
6
4
  class ConnectionPool
7
- attr_reader :mutex
5
+ # An array of connections currently being used
6
+ attr_reader :allocated
7
+
8
+ # An array of connections opened but not currently used
9
+ attr_reader :available_connections
10
+
11
+ # The proc used to create a new database connection.
12
+ attr_accessor :connection_proc
8
13
 
14
+ # The total number of connections opened, should
15
+ # be equal to available_connections.length +
16
+ # allocated.length
17
+ attr_reader :created_count
18
+ alias_method :size, :created_count
19
+
9
20
  # The maximum number of connections.
10
21
  attr_reader :max_size
11
22
 
12
- # The proc used to create a new connection.
13
- attr_accessor :connection_proc
23
+ # The mutex that protects access to the other internal vairables. You must use
24
+ # this if you want to manipulate the variables safely.
25
+ attr_reader :mutex
14
26
 
15
- attr_reader :available_connections, :allocated, :created_count
16
27
 
17
28
  # Constructs a new pool with a maximum size. If a block is supplied, it
18
29
  # is used to create new connections as they are needed.
19
30
  #
20
- # pool = ConnectionPool.new(10) {MyConnection.new(opts)}
31
+ # pool = ConnectionPool.new(:max_connections=>10) {MyConnection.new(opts)}
21
32
  #
22
33
  # The connection creation proc can be changed at any time by assigning a
23
34
  # Proc to pool#connection_proc.
24
35
  #
25
- # pool = ConnectionPool.new(10)
36
+ # pool = ConnectionPool.new(:max_connections=>10)
26
37
  # pool.connection_proc = proc {MyConnection.new(opts)}
27
- def initialize(max_size = 4, &block)
28
- @max_size = max_size
38
+ #
39
+ # The connection pool takes the following options:
40
+ #
41
+ # * :max_connections - The maximum number of connections the connection pool
42
+ # will open (default 4)
43
+ # * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
44
+ # to RuntimeError exceptions (default true)
45
+ # * :pool_reuse_connections - Which strategy to follow in regards to reusing connections:
46
+ # * :always - Always reuse a connection that belongs to the same thread
47
+ # * :allow - Only reuse a connection that belongs to the same thread if
48
+ # another cannot be acquired immediately (default)
49
+ # * :last_resort - Only reuse a connection that belongs to the same thread if
50
+ # the pool timeout has expired
51
+ # * :never - Never reuse a connection that belongs to the same thread
52
+ # * :pool_sleep_time - The amount of time to sleep before attempting to acquire
53
+ # a connection again (default 0.001)
54
+ # * :pool_timeout - The amount of seconds to wait to acquire a connection
55
+ # before raising a PoolTimeoutError (default 5)
56
+ def initialize(opts = {}, &block)
57
+ @max_size = opts[:max_connections] || 4
29
58
  @mutex = Mutex.new
30
59
  @connection_proc = block
31
60
 
32
61
  @available_connections = []
33
- @allocated = {}
62
+ @allocated = []
34
63
  @created_count = 0
64
+ @timeout = opts[:pool_timeout] || 5
65
+ @sleep_time = opts[:pool_sleep_time] || 0.001
66
+ @reuse_connections = opts[:pool_reuse_connections] || :allow
67
+ @convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
35
68
  end
36
69
 
37
- # Returns the number of created connections.
38
- def size
39
- @created_count
40
- end
41
-
42
- # Assigns a connection to the current thread, yielding the connection
43
- # to the supplied block.
70
+ # Chooses the first available connection, or if none are available,
71
+ # creates a new connection. Passes the connection to the supplied block:
44
72
  #
45
73
  # pool.hold {|conn| conn.execute('DROP TABLE posts')}
46
74
  #
47
- # Pool#hold is re-entrant, meaning it can be called recursively in
48
- # the same thread without blocking.
75
+ # Pool#hold can be re-entrant, meaning it can be called recursively in
76
+ # the same thread without blocking if the :pool_reuse_connections option
77
+ # was set to :always or :allow. Depending on the :pool_reuse_connections
78
+ # option you may get the connection currently used by the thread or a new connection.
49
79
  #
50
- # If no connection is available, Pool#hold will block until a connection
51
- # is available.
80
+ # If no connection is immediately available and the pool is already using the maximum
81
+ # number of connections, Pool#hold will block until a connection
82
+ # is available or the timeout expires. If the timeout expires before a
83
+ # connection can be acquired, a Sequel::Error::PoolTimeoutError is
84
+ # raised.
52
85
  def hold
53
- t = Thread.current
54
- if (conn = owned_connection(t))
55
- return yield(conn)
56
- end
57
- while !(conn = acquire(t))
58
- sleep 0.001
59
- end
60
86
  begin
61
- yield conn
62
- ensure
63
- release(t)
87
+ t = Thread.current
88
+ time = Time.new
89
+ timeout = time + @timeout
90
+ sleep_time = @sleep_time
91
+ reuse = @reuse_connections
92
+ if (reuse == :always) && (conn = owned_connection(t))
93
+ return yield(conn)
94
+ end
95
+ reuse = reuse == :allow ? true : false
96
+ until conn = acquire(t)
97
+ if reuse && (conn = owned_connection(t))
98
+ return yield(conn)
99
+ end
100
+ if Time.new > timeout
101
+ if (@reuse_connections == :last_resort) && (conn = owned_connection(t))
102
+ return yield(conn)
103
+ end
104
+ raise(::Sequel::Error::PoolTimeoutError)
105
+ end
106
+ sleep sleep_time
107
+ end
108
+ begin
109
+ yield conn
110
+ ensure
111
+ release(t, conn)
112
+ end
113
+ rescue Exception => e
114
+ raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
64
115
  end
65
- rescue Exception => e
66
- # if the error is not a StandardError it is converted into RuntimeError.
67
- raise e.is_a?(StandardError) ? e : e.message
68
116
  end
69
117
 
70
118
  # Removes all connection currently available, optionally yielding each
@@ -80,65 +128,84 @@ class ConnectionPool
80
128
  end
81
129
 
82
130
  private
83
- # Returns the connection owned by the supplied thread, if any.
84
- def owned_connection(thread)
85
- @mutex.synchronize {@allocated[thread]}
131
+
132
+ # Returns the connection owned by the supplied thread, if any.
133
+ def owned_connection(thread)
134
+ @mutex.synchronize do
135
+ x = @allocated.assoc(thread)
136
+ x[1] if x
86
137
  end
87
-
88
- # Assigns a connection to the supplied thread, if one is available.
89
- def acquire(thread)
90
- @mutex.synchronize do
91
- if conn = available
92
- @allocated[thread] = conn
93
- end
138
+ end
139
+
140
+ # Assigns a connection to the supplied thread, if one is available.
141
+ def acquire(thread)
142
+ @mutex.synchronize do
143
+ if conn = available
144
+ @allocated << [thread, conn]
145
+ conn
94
146
  end
95
147
  end
96
-
97
- # Returns an available connection. If no connection is available,
98
- # tries to create a new connection.
99
- def available
100
- @available_connections.pop || make_new
101
- end
102
-
103
- # Creates a new connection if the size of the pool is less than the
104
- # maximum size.
105
- def make_new
106
- if @created_count < @max_size
107
- @created_count += 1
108
- @connection_proc ? @connection_proc.call : \
109
- (raise Error, "No connection proc specified")
110
- end
148
+ end
149
+
150
+ # Returns an available connection. If no connection is available,
151
+ # tries to create a new connection.
152
+ def available
153
+ @available_connections.pop || make_new
154
+ end
155
+
156
+ # Creates a new connection if the size of the pool is less than the
157
+ # maximum size.
158
+ def make_new
159
+ if @created_count < @max_size
160
+ @created_count += 1
161
+ @connection_proc ? @connection_proc.call : \
162
+ (raise Error, "No connection proc specified")
111
163
  end
112
-
113
- # Releases the connection assigned to the supplied thread.
114
- def release(thread)
115
- @mutex.synchronize do
116
- @available_connections << @allocated[thread]
117
- @allocated.delete(thread)
118
- end
164
+ end
165
+
166
+ # Releases the connection assigned to the supplied thread.
167
+ def release(thread, conn)
168
+ @mutex.synchronize do
169
+ @allocated.delete([thread, conn])
170
+ @available_connections << conn
119
171
  end
172
+ end
120
173
  end
121
174
 
122
175
  # A SingleThreadedPool acts as a replacement for a ConnectionPool for use
123
176
  # in single-threaded applications. ConnectionPool imposes a substantial
124
177
  # performance penalty, so SingleThreadedPool is used to gain some speed.
178
+ #
179
+ # Note that using a single threaded pool with some adapters can cause
180
+ # errors in certain cases, see Sequel.single_threaded=.
125
181
  class SingleThreadedPool
182
+ # The single database connection for the pool
126
183
  attr_reader :conn
184
+
185
+ # The proc used to create a new database connection
127
186
  attr_writer :connection_proc
128
187
 
129
188
  # Initializes the instance with the supplied block as the connection_proc.
130
- def initialize(&block)
189
+ #
190
+ # The single threaded pool takes the following options:
191
+ #
192
+ # * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
193
+ # to RuntimeError exceptions (default true)
194
+ def initialize(opts={}, &block)
131
195
  @connection_proc = block
196
+ @convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
132
197
  end
133
198
 
134
199
  # Yields the connection to the supplied block. This method simulates the
135
200
  # ConnectionPool#hold API.
136
201
  def hold
137
- @conn ||= @connection_proc.call
138
- yield @conn
139
- rescue Exception => e
140
- # if the error is not a StandardError it is converted into RuntimeError.
141
- raise e.is_a?(StandardError) ? e : e.message
202
+ begin
203
+ @conn ||= @connection_proc.call
204
+ yield @conn
205
+ rescue Exception => e
206
+ # if the error is not a StandardError it is converted into RuntimeError.
207
+ raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
208
+ end
142
209
  end
143
210
 
144
211
  # Disconnects from the database. Once a connection is requested using
@@ -1,66 +1,201 @@
1
- # Enumerable extensions.
2
- module Enumerable
3
- # Invokes the specified method for each item, along with the supplied
4
- # arguments.
5
- def send_each(sym, *args)
6
- each {|i| i.send(sym, *args)}
7
- end
8
- end
9
-
10
- # Range extensions
11
- class Range
12
- # Returns the interval between the beginning and end of the range.
13
- def interval
14
- last - first
15
- end
16
- end
17
-
18
- # Object extensions
19
- class Object
20
- # Returns true if the object is a object of one of the classes
21
- def is_one_of?(*classes)
22
- classes.each {|c| return c if is_a?(c)}
23
- nil
24
- end
25
-
26
- # Objects are blank if they respond true to empty?
27
- def blank?
28
- nil? || (respond_to?(:empty?) && empty?)
29
- end
30
- end
31
-
32
- class Numeric
33
- # Numerics are never blank (not even 0)
34
- def blank?
35
- false
36
- end
37
- end
38
-
39
- class NilClass
40
- # nil is always blank
41
- def blank?
42
- true
43
- end
44
- end
45
-
46
- class TrueClass
47
- # true is never blank
48
- def blank?
49
- false
50
- end
51
- end
52
-
53
- class FalseClass
54
- # false is always blank
55
- def blank?
56
- true
57
- end
58
- end
59
-
60
- class String
61
- BLANK_STRING_REGEXP = /\A\s*\z/
62
- # Strings are blank if they are empty or include only whitespace
63
- def blank?
64
- empty? || BLANK_STRING_REGEXP.match(self)
65
- end
66
- end
1
+ # This file includes augmentations to the core ruby classes the Sequel uses,
2
+ # which are unrelated to the creation of SQL. It includes common
3
+ # idioms to reduce the amount of code duplication.
4
+
5
+ class Array
6
+ # True if the array is not empty and all of its elements are
7
+ # arrays of size 2. This is used to determine if the array
8
+ # could be a specifier of conditions, used similarly to a hash
9
+ # but allowing for duplicate keys.
10
+ #
11
+ # hash.to_a.all_two_pairs? # => true unless hash is empty
12
+ def all_two_pairs?
13
+ !empty? && all?{|i| (Array === i) && (i.length == 2)}
14
+ end
15
+
16
+ # Removes and returns the last member of the array if it is a hash. Otherwise,
17
+ # an empty hash is returned This method is useful when writing methods that
18
+ # take an options hash as the last parameter. For example:
19
+ #
20
+ # def validate_each(*args, &block)
21
+ # opts = args.extract_options!
22
+ # ...
23
+ # end
24
+ def extract_options!
25
+ last.is_a?(Hash) ? pop : {}
26
+ end
27
+ end
28
+
29
+ module Enumerable
30
+ # Invokes the specified method for each item, along with the supplied
31
+ # arguments.
32
+ def send_each(sym, *args)
33
+ each{|i| i.send(sym, *args)}
34
+ end
35
+ end
36
+
37
+ class FalseClass
38
+ # false is always blank
39
+ def blank?
40
+ true
41
+ end
42
+ end
43
+
44
+ # Add some metaprogramming methods to avoid class << self
45
+ class Module
46
+ # Defines an instance method within a class/module
47
+ def class_def(name, &block)
48
+ class_eval{define_method(name, &block)}
49
+ end
50
+
51
+ private
52
+
53
+ # Define instance method(s) that calls class method(s) of the
54
+ # same name. Replaces the construct:
55
+ #
56
+ # define_method(meth){self.class.send(meth)}
57
+ def class_attr_reader(*meths)
58
+ meths.each{|meth| define_method(meth){self.class.send(meth)}}
59
+ end
60
+
61
+ # Create an alias for a singleton/class method.
62
+ # Replaces the construct:
63
+ #
64
+ # class << self
65
+ # alias_method to, from
66
+ # end
67
+ def metaalias(to, from)
68
+ metaclass.instance_eval{alias_method to, from}
69
+ end
70
+
71
+ # Make a singleton/class attribute accessor method(s).
72
+ # Replaces the construct:
73
+ #
74
+ # class << self
75
+ # attr_accessor *meths
76
+ # end
77
+ def metaattr_accessor(*meths)
78
+ metaclass.instance_eval{attr_accessor(*meths)}
79
+ end
80
+
81
+ # Make a singleton/class method(s) private.
82
+ # Make a singleton/class attribute reader method(s).
83
+ # Replaces the construct:
84
+ #
85
+ # class << self
86
+ # attr_reader *meths
87
+ # end
88
+ def metaattr_reader(*meths)
89
+ metaclass.instance_eval{attr_reader(*meths)}
90
+ end
91
+
92
+ # Make a singleton/class method(s) private.
93
+ # Replaces the construct:
94
+ #
95
+ # class << self
96
+ # private *meths
97
+ # end
98
+ def metaprivate(*meths)
99
+ metaclass.instance_eval{private(*meths)}
100
+ end
101
+ end
102
+
103
+ # Helpers from Metaid and a bit more
104
+ class Object
105
+ # Objects are blank if they respond true to empty?
106
+ def blank?
107
+ respond_to?(:empty?) && empty?
108
+ end
109
+
110
+ # Returns true if the object is an instance of one of the classes
111
+ def is_one_of?(*classes)
112
+ !!classes.find{|c| is_a?(c)}
113
+ end
114
+
115
+ # Add methods to the object's metaclass
116
+ def meta_def(name, &block)
117
+ meta_eval{define_method(name, &block)}
118
+ end
119
+
120
+ # Evaluate the block in the context of the object's metaclass
121
+ def meta_eval(&block)
122
+ metaclass.instance_eval(&block)
123
+ end
124
+
125
+ # The hidden singleton lurks behind everyone
126
+ def metaclass
127
+ class << self
128
+ self
129
+ end
130
+ end
131
+ end
132
+
133
+ class NilClass
134
+ # nil is always blank
135
+ def blank?
136
+ true
137
+ end
138
+ end
139
+
140
+ class Numeric
141
+ # Numerics are never blank (not even 0)
142
+ def blank?
143
+ false
144
+ end
145
+ end
146
+
147
+ class Range
148
+ # Returns the interval between the beginning and end of the range.
149
+ #
150
+ # For exclusive ranges, is one less than the inclusive range:
151
+ #
152
+ # (0..10).interval # => 10
153
+ # (0...10).interval # => 9
154
+ #
155
+ # Only works for numeric ranges, for other ranges the result is undefined,
156
+ # and the method may raise an error.
157
+ def interval
158
+ last - first - (exclude_end? ? 1 : 0)
159
+ end
160
+ end
161
+
162
+ class String
163
+ # Strings are blank if they are empty or include only whitespace
164
+ def blank?
165
+ strip.empty?
166
+ end
167
+
168
+ # Converts a string into a Date object.
169
+ def to_date
170
+ begin
171
+ Date.parse(self)
172
+ rescue => e
173
+ raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
174
+ end
175
+ end
176
+
177
+ # Converts a string into a DateTime object.
178
+ def to_datetime
179
+ begin
180
+ DateTime.parse(self)
181
+ rescue => e
182
+ raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
183
+ end
184
+ end
185
+
186
+ # Converts a string into a Time object.
187
+ def to_time
188
+ begin
189
+ Time.parse(self)
190
+ rescue => e
191
+ raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
192
+ end
193
+ end
194
+ end
195
+
196
+ class TrueClass
197
+ # true is never blank
198
+ def blank?
199
+ false
200
+ end
201
+ end