sequel 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1551 -4
- data/README +306 -19
- data/Rakefile +84 -56
- data/bin/sequel +106 -0
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +182 -0
- data/lib/sequel_core.rb +136 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
- data/lib/sequel_core/adapters/ado.rb +80 -0
- data/lib/sequel_core/adapters/db2.rb +148 -0
- data/lib/sequel_core/adapters/dbi.rb +117 -0
- data/lib/sequel_core/adapters/informix.rb +78 -0
- data/lib/sequel_core/adapters/jdbc.rb +186 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
- data/lib/sequel_core/adapters/mysql.rb +231 -0
- data/lib/sequel_core/adapters/odbc.rb +155 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
- data/lib/sequel_core/adapters/openbase.rb +64 -0
- data/lib/sequel_core/adapters/oracle.rb +170 -0
- data/lib/sequel_core/adapters/postgres.rb +199 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
- data/lib/sequel_core/adapters/sqlite.rb +138 -0
- data/lib/sequel_core/connection_pool.rb +194 -0
- data/lib/sequel_core/core_ext.rb +203 -0
- data/lib/sequel_core/core_sql.rb +184 -0
- data/lib/sequel_core/database.rb +471 -0
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/dataset.rb +457 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +245 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +889 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +42 -0
- data/lib/sequel_core/migration.rb +187 -0
- data/lib/sequel_core/object_graph.rb +216 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/schema/generator.rb +239 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/sql.rb +812 -0
- data/lib/sequel_model.rb +5 -1
- data/lib/sequel_model/association_reflection.rb +3 -8
- data/lib/sequel_model/base.rb +15 -10
- data/lib/sequel_model/inflector.rb +3 -5
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +11 -3
- data/lib/sequel_model/schema.rb +4 -4
- data/lib/sequel_model/validations.rb +6 -1
- data/spec/adapters/ado_spec.rb +17 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +764 -0
- data/spec/adapters/oracle_spec.rb +222 -0
- data/spec/adapters/postgres_spec.rb +441 -0
- data/spec/adapters/spec_helper.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +400 -0
- data/spec/integration/dataset_test.rb +51 -0
- data/spec/integration/eager_loader_test.rb +702 -0
- data/spec/integration/schema_test.rb +102 -0
- data/spec/integration/spec_helper.rb +44 -0
- data/spec/integration/type_test.rb +43 -0
- data/spec/rcov.opts +2 -0
- data/spec/sequel_core/connection_pool_spec.rb +363 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +427 -0
- data/spec/sequel_core/database_spec.rb +964 -0
- data/spec/sequel_core/dataset_spec.rb +2977 -0
- data/spec/sequel_core/expression_filters_spec.rb +346 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +234 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +122 -0
- data/spec/sequel_core/schema_spec.rb +497 -0
- data/spec/sequel_core/spec_helper.rb +51 -0
- data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
- data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
- data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
- data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
- data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
- data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
- data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
- data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
- data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
- data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
- data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
- data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
- data/spec/spec_config.rb +9 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +110 -37
- data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'sequel_core/adapters/shared/sqlite'
|
3
|
+
|
4
|
+
module Sequel
|
5
|
+
module SQLite
|
6
|
+
class Database < Sequel::Database
|
7
|
+
include ::Sequel::SQLite::DatabaseMethods
|
8
|
+
|
9
|
+
set_adapter_scheme :sqlite
|
10
|
+
|
11
|
+
def self.uri_to_options(uri) # :nodoc:
|
12
|
+
{ :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
|
13
|
+
end
|
14
|
+
|
15
|
+
private_class_method :uri_to_options
|
16
|
+
|
17
|
+
def connect
|
18
|
+
@opts[:database] = ':memory:' if @opts[:database].blank?
|
19
|
+
db = ::SQLite3::Database.new(@opts[:database])
|
20
|
+
db.busy_timeout(@opts.fetch(:timeout, 5000))
|
21
|
+
db.type_translation = true
|
22
|
+
# fix for timestamp translation
|
23
|
+
db.translator.add_translator("timestamp") do |t, v|
|
24
|
+
v =~ /^\d+$/ ? Time.at(v.to_i) : Time.parse(v)
|
25
|
+
end
|
26
|
+
db
|
27
|
+
end
|
28
|
+
|
29
|
+
def dataset(opts = nil)
|
30
|
+
SQLite::Dataset.new(self, opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def disconnect
|
34
|
+
@pool.disconnect {|c| c.close}
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute(sql)
|
38
|
+
begin
|
39
|
+
log_info(sql)
|
40
|
+
@pool.hold {|conn| conn.execute_batch(sql); conn.changes}
|
41
|
+
rescue SQLite3::Exception => e
|
42
|
+
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute_insert(sql)
|
47
|
+
begin
|
48
|
+
log_info(sql)
|
49
|
+
@pool.hold {|conn| conn.execute(sql); conn.last_insert_row_id}
|
50
|
+
rescue SQLite3::Exception => e
|
51
|
+
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def single_value(sql)
|
56
|
+
begin
|
57
|
+
log_info(sql)
|
58
|
+
@pool.hold {|conn| conn.get_first_value(sql)}
|
59
|
+
rescue SQLite3::Exception => e
|
60
|
+
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def execute_select(sql, &block)
|
65
|
+
begin
|
66
|
+
log_info(sql)
|
67
|
+
@pool.hold {|conn| conn.query(sql, &block)}
|
68
|
+
rescue SQLite3::Exception => e
|
69
|
+
raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def transaction(&block)
|
74
|
+
@pool.hold do |conn|
|
75
|
+
if conn.transaction_active?
|
76
|
+
return yield(conn)
|
77
|
+
end
|
78
|
+
begin
|
79
|
+
result = nil
|
80
|
+
conn.transaction {result = yield(conn)}
|
81
|
+
result
|
82
|
+
rescue ::Exception => e
|
83
|
+
raise (SQLite3::Exception === e ? Error.new(e.message) : e) unless Error::Rollback === e
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def connection_pool_default_options
|
91
|
+
o = super.merge(:pool_convert_exceptions=>false)
|
92
|
+
# Default to only a single connection if a memory database is used,
|
93
|
+
# because otherwise each connection will get a separate database
|
94
|
+
o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
|
95
|
+
o
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Dataset < Sequel::Dataset
|
100
|
+
include ::Sequel::SQLite::DatasetMethods
|
101
|
+
|
102
|
+
EXPLAIN = 'EXPLAIN %s'.freeze
|
103
|
+
|
104
|
+
def explain
|
105
|
+
res = []
|
106
|
+
@db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
|
107
|
+
res
|
108
|
+
end
|
109
|
+
|
110
|
+
def fetch_rows(sql)
|
111
|
+
@db.execute_select(sql) do |result|
|
112
|
+
@columns = result.columns.map {|c| c.to_sym}
|
113
|
+
column_count = @columns.size
|
114
|
+
result.each do |values|
|
115
|
+
row = {}
|
116
|
+
column_count.times {|i| row[@columns[i]] = values[i]}
|
117
|
+
yield row
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def literal(v)
|
123
|
+
case v
|
124
|
+
when LiteralString
|
125
|
+
v
|
126
|
+
when String
|
127
|
+
"'#{::SQLite3::Database.quote(v)}'"
|
128
|
+
when Time
|
129
|
+
literal(v.iso8601)
|
130
|
+
when Date, DateTime
|
131
|
+
literal(v.to_s)
|
132
|
+
else
|
133
|
+
super
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# A ConnectionPool manages access to database connections by keeping
|
2
|
+
# multiple connections and giving threads exclusive access to each
|
3
|
+
# connection.
|
4
|
+
class Sequel::ConnectionPool
|
5
|
+
# A hash of connections currently being used, key is the Thread,
|
6
|
+
# value is the connection.
|
7
|
+
attr_reader :allocated
|
8
|
+
|
9
|
+
# An array of connections opened but not currently used
|
10
|
+
attr_reader :available_connections
|
11
|
+
|
12
|
+
# The proc used to create a new database connection.
|
13
|
+
attr_accessor :connection_proc
|
14
|
+
|
15
|
+
# The total number of connections opened, should
|
16
|
+
# be equal to available_connections.length +
|
17
|
+
# allocated.length
|
18
|
+
attr_reader :created_count
|
19
|
+
alias_method :size, :created_count
|
20
|
+
|
21
|
+
# The maximum number of connections.
|
22
|
+
attr_reader :max_size
|
23
|
+
|
24
|
+
# The mutex that protects access to the other internal vairables. You must use
|
25
|
+
# this if you want to manipulate the variables safely.
|
26
|
+
attr_reader :mutex
|
27
|
+
|
28
|
+
|
29
|
+
# Constructs a new pool with a maximum size. If a block is supplied, it
|
30
|
+
# is used to create new connections as they are needed.
|
31
|
+
#
|
32
|
+
# pool = ConnectionPool.new(:max_connections=>10) {MyConnection.new(opts)}
|
33
|
+
#
|
34
|
+
# The connection creation proc can be changed at any time by assigning a
|
35
|
+
# Proc to pool#connection_proc.
|
36
|
+
#
|
37
|
+
# pool = ConnectionPool.new(:max_connections=>10)
|
38
|
+
# pool.connection_proc = proc {MyConnection.new(opts)}
|
39
|
+
#
|
40
|
+
# The connection pool takes the following options:
|
41
|
+
#
|
42
|
+
# * :max_connections - The maximum number of connections the connection pool
|
43
|
+
# will open (default 4)
|
44
|
+
# * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
|
45
|
+
# to RuntimeError exceptions (default true)
|
46
|
+
# * :pool_sleep_time - The amount of time to sleep before attempting to acquire
|
47
|
+
# a connection again (default 0.001)
|
48
|
+
# * :pool_timeout - The amount of seconds to wait to acquire a connection
|
49
|
+
# before raising a PoolTimeoutError (default 5)
|
50
|
+
def initialize(opts = {}, &block)
|
51
|
+
@max_size = opts[:max_connections] || 4
|
52
|
+
@mutex = Mutex.new
|
53
|
+
@connection_proc = block
|
54
|
+
|
55
|
+
@available_connections = []
|
56
|
+
@allocated = {}
|
57
|
+
@created_count = 0
|
58
|
+
@timeout = opts[:pool_timeout] || 5
|
59
|
+
@sleep_time = opts[:pool_sleep_time] || 0.001
|
60
|
+
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
61
|
+
end
|
62
|
+
|
63
|
+
# Chooses the first available connection, or if none are available,
|
64
|
+
# creates a new connection. Passes the connection to the supplied block:
|
65
|
+
#
|
66
|
+
# pool.hold {|conn| conn.execute('DROP TABLE posts')}
|
67
|
+
#
|
68
|
+
# Pool#hold is re-entrant, meaning it can be called recursively in
|
69
|
+
# the same thread without blocking.
|
70
|
+
#
|
71
|
+
# If no connection is immediately available and the pool is already using the maximum
|
72
|
+
# number of connections, Pool#hold will block until a connection
|
73
|
+
# is available or the timeout expires. If the timeout expires before a
|
74
|
+
# connection can be acquired, a Sequel::Error::PoolTimeoutError is
|
75
|
+
# raised.
|
76
|
+
def hold
|
77
|
+
begin
|
78
|
+
t = Thread.current
|
79
|
+
time = Time.new
|
80
|
+
timeout = time + @timeout
|
81
|
+
sleep_time = @sleep_time
|
82
|
+
if conn = owned_connection(t)
|
83
|
+
return yield(conn)
|
84
|
+
end
|
85
|
+
until conn = acquire(t)
|
86
|
+
raise(::Sequel::Error::PoolTimeoutError) if Time.new > timeout
|
87
|
+
sleep sleep_time
|
88
|
+
end
|
89
|
+
begin
|
90
|
+
yield conn
|
91
|
+
ensure
|
92
|
+
release(t, conn)
|
93
|
+
end
|
94
|
+
rescue Exception => e
|
95
|
+
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Removes all connection currently available, optionally yielding each
|
100
|
+
# connection to the given block. This method has the effect of
|
101
|
+
# disconnecting from the database. Once a connection is requested using
|
102
|
+
# #hold, the connection pool creates new connections to the database.
|
103
|
+
def disconnect(&block)
|
104
|
+
@mutex.synchronize do
|
105
|
+
@available_connections.each {|c| block[c]} if block
|
106
|
+
@available_connections = []
|
107
|
+
@created_count = @allocated.size
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
# Returns the connection owned by the supplied thread, if any.
|
114
|
+
def owned_connection(thread)
|
115
|
+
@mutex.synchronize{@allocated[thread]}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Assigns a connection to the supplied thread, if one is available.
|
119
|
+
def acquire(thread)
|
120
|
+
@mutex.synchronize do
|
121
|
+
if conn = available
|
122
|
+
@allocated[thread] = conn
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns an available connection. If no connection is available,
|
128
|
+
# tries to create a new connection.
|
129
|
+
def available
|
130
|
+
@available_connections.pop || make_new
|
131
|
+
end
|
132
|
+
|
133
|
+
# Creates a new connection if the size of the pool is less than the
|
134
|
+
# maximum size.
|
135
|
+
def make_new
|
136
|
+
if @created_count < @max_size
|
137
|
+
@created_count += 1
|
138
|
+
@connection_proc ? @connection_proc.call : \
|
139
|
+
(raise Error, "No connection proc specified")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Releases the connection assigned to the supplied thread.
|
144
|
+
def release(thread, conn)
|
145
|
+
@mutex.synchronize do
|
146
|
+
@allocated.delete(thread)
|
147
|
+
@available_connections << conn
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# A SingleThreadedPool acts as a replacement for a ConnectionPool for use
|
153
|
+
# in single-threaded applications. ConnectionPool imposes a substantial
|
154
|
+
# performance penalty, so SingleThreadedPool is used to gain some speed.
|
155
|
+
#
|
156
|
+
# Note that using a single threaded pool with some adapters can cause
|
157
|
+
# errors in certain cases, see Sequel.single_threaded=.
|
158
|
+
class Sequel::SingleThreadedPool
|
159
|
+
# The single database connection for the pool
|
160
|
+
attr_reader :conn
|
161
|
+
|
162
|
+
# The proc used to create a new database connection
|
163
|
+
attr_writer :connection_proc
|
164
|
+
|
165
|
+
# Initializes the instance with the supplied block as the connection_proc.
|
166
|
+
#
|
167
|
+
# The single threaded pool takes the following options:
|
168
|
+
#
|
169
|
+
# * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
|
170
|
+
# to RuntimeError exceptions (default true)
|
171
|
+
def initialize(opts={}, &block)
|
172
|
+
@connection_proc = block
|
173
|
+
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
174
|
+
end
|
175
|
+
|
176
|
+
# Yields the connection to the supplied block. This method simulates the
|
177
|
+
# ConnectionPool#hold API.
|
178
|
+
def hold
|
179
|
+
begin
|
180
|
+
@conn ||= @connection_proc.call
|
181
|
+
yield @conn
|
182
|
+
rescue Exception => e
|
183
|
+
# if the error is not a StandardError it is converted into RuntimeError.
|
184
|
+
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Disconnects from the database. Once a connection is requested using
|
189
|
+
# #hold, the connection is reestablished.
|
190
|
+
def disconnect(&block)
|
191
|
+
block[@conn] if block && @conn
|
192
|
+
@conn = nil
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
if RUBY_VERSION < '1.9.0'
|
2
|
+
class Hash
|
3
|
+
alias key index
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Array
|
8
|
+
# True if the array is not empty and all of its elements are
|
9
|
+
# arrays of size 2. This is used to determine if the array
|
10
|
+
# could be a specifier of conditions, used similarly to a hash
|
11
|
+
# but allowing for duplicate keys.
|
12
|
+
#
|
13
|
+
# hash.to_a.all_two_pairs? # => true unless hash is empty
|
14
|
+
def all_two_pairs?
|
15
|
+
!empty? && all?{|i| (Array === i) && (i.length == 2)}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Removes and returns the last member of the array if it is a hash. Otherwise,
|
19
|
+
# an empty hash is returned This method is useful when writing methods that
|
20
|
+
# take an options hash as the last parameter. For example:
|
21
|
+
#
|
22
|
+
# def validate_each(*args, &block)
|
23
|
+
# opts = args.extract_options!
|
24
|
+
# ...
|
25
|
+
# end
|
26
|
+
def extract_options!
|
27
|
+
last.is_a?(Hash) ? pop : {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Enumerable
|
32
|
+
# Invokes the specified method for each item, along with the supplied
|
33
|
+
# arguments.
|
34
|
+
def send_each(sym, *args)
|
35
|
+
each{|i| i.send(sym, *args)}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class FalseClass
|
40
|
+
# false is always blank
|
41
|
+
def blank?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Add some metaprogramming methods to avoid class << self
|
47
|
+
class Module
|
48
|
+
# Defines an instance method within a class/module
|
49
|
+
def class_def(name, &block)
|
50
|
+
class_eval{define_method(name, &block)}
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Define instance method(s) that calls class method(s) of the
|
56
|
+
# same name. Replaces the construct:
|
57
|
+
#
|
58
|
+
# define_method(meth){self.class.send(meth)}
|
59
|
+
def class_attr_reader(*meths)
|
60
|
+
meths.each{|meth| define_method(meth){self.class.send(meth)}}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create an alias for a singleton/class method.
|
64
|
+
# Replaces the construct:
|
65
|
+
#
|
66
|
+
# class << self
|
67
|
+
# alias_method to, from
|
68
|
+
# end
|
69
|
+
def metaalias(to, from)
|
70
|
+
meta_eval{alias_method to, from}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Make a singleton/class attribute accessor method(s).
|
74
|
+
# Replaces the construct:
|
75
|
+
#
|
76
|
+
# class << self
|
77
|
+
# attr_accessor *meths
|
78
|
+
# end
|
79
|
+
def metaattr_accessor(*meths)
|
80
|
+
meta_eval{attr_accessor(*meths)}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Make a singleton/class method(s) private.
|
84
|
+
# Make a singleton/class attribute reader method(s).
|
85
|
+
# Replaces the construct:
|
86
|
+
#
|
87
|
+
# class << self
|
88
|
+
# attr_reader *meths
|
89
|
+
# end
|
90
|
+
def metaattr_reader(*meths)
|
91
|
+
meta_eval{attr_reader(*meths)}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Helpers from Metaid and a bit more
|
96
|
+
class Object
|
97
|
+
# Objects are blank if they respond true to empty?
|
98
|
+
def blank?
|
99
|
+
respond_to?(:empty?) && empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns true if the object is an instance of one of the classes
|
103
|
+
def is_one_of?(*classes)
|
104
|
+
!!classes.find{|c| is_a?(c)}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add methods to the object's metaclass
|
108
|
+
def meta_def(name, &block)
|
109
|
+
meta_eval{define_method(name, &block)}
|
110
|
+
end
|
111
|
+
|
112
|
+
# Evaluate the block in the context of the object's metaclass
|
113
|
+
def meta_eval(&block)
|
114
|
+
metaclass.instance_eval(&block)
|
115
|
+
end
|
116
|
+
|
117
|
+
# The hidden singleton lurks behind everyone
|
118
|
+
def metaclass
|
119
|
+
class << self
|
120
|
+
self
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class NilClass
|
126
|
+
# nil is always blank
|
127
|
+
def blank?
|
128
|
+
true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Numeric
|
133
|
+
# Numerics are never blank (not even 0)
|
134
|
+
def blank?
|
135
|
+
false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Range
|
140
|
+
# Returns the interval between the beginning and end of the range.
|
141
|
+
#
|
142
|
+
# For exclusive ranges, is one less than the inclusive range:
|
143
|
+
#
|
144
|
+
# (0..10).interval # => 10
|
145
|
+
# (0...10).interval # => 9
|
146
|
+
#
|
147
|
+
# Only works for numeric ranges, for other ranges the result is undefined,
|
148
|
+
# and the method may raise an error.
|
149
|
+
def interval
|
150
|
+
last - first - (exclude_end? ? 1 : 0)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class String
|
155
|
+
# Strings are blank if they are empty or include only whitespace
|
156
|
+
def blank?
|
157
|
+
strip.empty?
|
158
|
+
end
|
159
|
+
|
160
|
+
# Converts a string into a Date object.
|
161
|
+
def to_date
|
162
|
+
begin
|
163
|
+
Date.parse(self)
|
164
|
+
rescue => e
|
165
|
+
raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Converts a string into a DateTime object.
|
170
|
+
def to_datetime
|
171
|
+
begin
|
172
|
+
DateTime.parse(self)
|
173
|
+
rescue => e
|
174
|
+
raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Converts a string into a Time or DateTime object, depending on the
|
179
|
+
# value of Sequel.datetime_class
|
180
|
+
def to_sequel_time
|
181
|
+
begin
|
182
|
+
Sequel.datetime_class.parse(self)
|
183
|
+
rescue => e
|
184
|
+
raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Converts a string into a Time object.
|
189
|
+
def to_time
|
190
|
+
begin
|
191
|
+
Time.parse(self)
|
192
|
+
rescue => e
|
193
|
+
raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class TrueClass
|
199
|
+
# true is never blank
|
200
|
+
def blank?
|
201
|
+
false
|
202
|
+
end
|
203
|
+
end
|