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.
- data/CHANGELOG +116 -0
- data/COPYING +19 -19
- data/README +83 -32
- data/Rakefile +9 -20
- data/bin/sequel +43 -112
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +257 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
- data/lib/sequel_core/adapters/ado.rb +3 -1
- data/lib/sequel_core/adapters/db2.rb +4 -2
- data/lib/sequel_core/adapters/dbi.rb +127 -113
- data/lib/sequel_core/adapters/informix.rb +4 -2
- data/lib/sequel_core/adapters/jdbc.rb +5 -3
- data/lib/sequel_core/adapters/mysql.rb +112 -46
- data/lib/sequel_core/adapters/odbc.rb +5 -7
- data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
- data/lib/sequel_core/adapters/openbase.rb +3 -1
- data/lib/sequel_core/adapters/oracle.rb +11 -9
- data/lib/sequel_core/adapters/postgres.rb +261 -262
- data/lib/sequel_core/adapters/sqlite.rb +72 -22
- data/lib/sequel_core/connection_pool.rb +140 -73
- data/lib/sequel_core/core_ext.rb +201 -66
- data/lib/sequel_core/core_sql.rb +123 -153
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/database.rb +321 -338
- data/lib/sequel_core/dataset/callback.rb +11 -12
- data/lib/sequel_core/dataset/convenience.rb +213 -240
- data/lib/sequel_core/dataset/pagination.rb +58 -43
- data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sequelizer.rb +41 -373
- data/lib/sequel_core/dataset/sql.rb +741 -632
- data/lib/sequel_core/dataset.rb +183 -168
- data/lib/sequel_core/deprecated.rb +1 -169
- data/lib/sequel_core/exceptions.rb +24 -19
- data/lib/sequel_core/migration.rb +44 -52
- data/lib/sequel_core/object_graph.rb +43 -42
- data/lib/sequel_core/pretty_table.rb +71 -76
- data/lib/sequel_core/schema/generator.rb +163 -105
- data/lib/sequel_core/schema/sql.rb +250 -93
- data/lib/sequel_core/schema.rb +2 -8
- data/lib/sequel_core/sql.rb +394 -0
- data/lib/sequel_core/worker.rb +37 -27
- data/lib/sequel_core.rb +99 -45
- data/spec/adapters/informix_spec.rb +0 -1
- data/spec/adapters/mysql_spec.rb +177 -124
- data/spec/adapters/oracle_spec.rb +0 -1
- data/spec/adapters/postgres_spec.rb +98 -58
- data/spec/adapters/sqlite_spec.rb +45 -4
- data/spec/blockless_filters_spec.rb +269 -0
- data/spec/connection_pool_spec.rb +21 -18
- data/spec/core_ext_spec.rb +169 -19
- data/spec/core_sql_spec.rb +56 -49
- data/spec/database_spec.rb +78 -17
- data/spec/dataset_spec.rb +300 -428
- data/spec/migration_spec.rb +1 -1
- data/spec/object_graph_spec.rb +5 -11
- data/spec/rcov.opts +1 -1
- data/spec/schema_generator_spec.rb +16 -4
- data/spec/schema_spec.rb +89 -10
- data/spec/sequelizer_spec.rb +56 -56
- data/spec/spec.opts +0 -5
- data/spec/spec_config.rb +7 -0
- data/spec/spec_config.rb.example +5 -5
- data/spec/spec_helper.rb +6 -0
- data/spec/worker_spec.rb +1 -1
- metadata +78 -63
@@ -10,9 +10,7 @@ module Sequel
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def connect
|
13
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
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
|
-
|
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
|
13
|
-
|
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
|
-
|
28
|
-
|
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
|
-
#
|
38
|
-
|
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
|
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
|
51
|
-
#
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
data/lib/sequel_core/core_ext.rb
CHANGED
@@ -1,66 +1,201 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
class
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
class
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|