sequel 3.37.0 → 3.38.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README.rdoc +82 -58
- data/Rakefile +6 -5
- data/bin/sequel +1 -1
- data/doc/active_record.rdoc +67 -52
- data/doc/advanced_associations.rdoc +33 -48
- data/doc/association_basics.rdoc +41 -51
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +47 -43
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +4 -5
- data/doc/model_hooks.rdoc +3 -3
- data/doc/object_model.rdoc +31 -25
- data/doc/opening_databases.rdoc +19 -5
- data/doc/prepared_statements.rdoc +2 -2
- data/doc/querying.rdoc +109 -52
- data/doc/reflection.rdoc +6 -6
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/schema_modification.rdoc +22 -13
- data/doc/sharding.rdoc +8 -9
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +47 -7
- data/doc/thread_safety.rdoc +1 -1
- data/doc/transactions.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +29 -43
- data/lib/sequel/adapters/do/postgres.rb +1 -4
- data/lib/sequel/adapters/jdbc.rb +14 -3
- data/lib/sequel/adapters/jdbc/db2.rb +9 -0
- data/lib/sequel/adapters/jdbc/derby.rb +41 -4
- data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
- data/lib/sequel/adapters/mock.rb +10 -4
- data/lib/sequel/adapters/postgres.rb +1 -28
- data/lib/sequel/adapters/shared/mssql.rb +23 -13
- data/lib/sequel/adapters/shared/postgres.rb +46 -0
- data/lib/sequel/adapters/swift.rb +21 -13
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +4 -5
- data/lib/sequel/adapters/swift/sqlite.rb +2 -1
- data/lib/sequel/adapters/tinytds.rb +14 -2
- data/lib/sequel/adapters/utils/pg_types.rb +5 -0
- data/lib/sequel/core.rb +29 -17
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/dataset/actions.rb +5 -6
- data/lib/sequel/dataset/query.rb +7 -7
- data/lib/sequel/dataset/sql.rb +5 -18
- data/lib/sequel/extensions/core_extensions.rb +8 -12
- data/lib/sequel/extensions/pg_array.rb +59 -33
- data/lib/sequel/extensions/pg_array_ops.rb +32 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
- data/lib/sequel/extensions/pg_hstore.rb +32 -17
- data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
- data/lib/sequel/extensions/pg_inet.rb +1 -2
- data/lib/sequel/extensions/pg_interval.rb +0 -1
- data/lib/sequel/extensions/pg_json.rb +41 -23
- data/lib/sequel/extensions/pg_range.rb +36 -11
- data/lib/sequel/extensions/pg_range_ops.rb +32 -4
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +164 -0
- data/lib/sequel/extensions/query.rb +3 -3
- data/lib/sequel/extensions/schema_dumper.rb +7 -8
- data/lib/sequel/extensions/select_remove.rb +1 -1
- data/lib/sequel/model/base.rb +1 -0
- data/lib/sequel/no_core_ext.rb +1 -1
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/validation_helpers.rb +31 -0
- data/lib/sequel/sql.rb +64 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +37 -12
- data/spec/adapters/mysql_spec.rb +39 -75
- data/spec/adapters/oracle_spec.rb +11 -11
- data/spec/adapters/postgres_spec.rb +414 -237
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +14 -14
- data/spec/core/database_spec.rb +6 -6
- data/spec/core/dataset_spec.rb +169 -205
- data/spec/core/expression_filters_spec.rb +182 -295
- data/spec/core/object_graph_spec.rb +6 -6
- data/spec/core/schema_spec.rb +14 -14
- data/spec/core/spec_helper.rb +1 -0
- data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
- data/spec/extensions/columns_introspection_spec.rb +5 -5
- data/spec/extensions/hook_class_methods_spec.rb +28 -36
- data/spec/extensions/many_through_many_spec.rb +4 -4
- data/spec/extensions/pg_array_ops_spec.rb +15 -7
- data/spec/extensions/pg_array_spec.rb +81 -48
- data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
- data/spec/extensions/pg_hstore_spec.rb +66 -65
- data/spec/extensions/pg_inet_spec.rb +2 -4
- data/spec/extensions/pg_interval_spec.rb +2 -3
- data/spec/extensions/pg_json_spec.rb +20 -18
- data/spec/extensions/pg_range_ops_spec.rb +11 -4
- data/spec/extensions/pg_range_spec.rb +30 -7
- data/spec/extensions/pg_row_ops_spec.rb +48 -0
- data/spec/extensions/pg_row_plugin_spec.rb +45 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/query_literals_spec.rb +11 -11
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +20 -4
- data/spec/extensions/schema_spec.rb +18 -41
- data/spec/extensions/select_remove_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +4 -8
- data/spec/extensions/to_dot_spec.rb +5 -5
- data/spec/extensions/validation_class_methods_spec.rb +28 -16
- data/spec/integration/associations_test.rb +20 -20
- data/spec/integration/dataset_test.rb +98 -98
- data/spec/integration/eager_loader_test.rb +13 -27
- data/spec/integration/plugin_test.rb +5 -5
- data/spec/integration/prepared_statement_test.rb +22 -13
- data/spec/integration/schema_test.rb +28 -18
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +2 -2
- data/spec/integration/type_test.rb +15 -6
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +4 -4
- data/spec/model/base_spec.rb +5 -5
- data/spec/model/eager_loading_spec.rb +15 -15
- data/spec/model/model_spec.rb +32 -32
- data/spec/model/record_spec.rb +16 -0
- data/spec/model/spec_helper.rb +2 -6
- data/spec/model/validations_spec.rb +1 -1
- metadata +16 -4
@@ -0,0 +1,164 @@
|
|
1
|
+
# The pg_row_ops extension adds support to Sequel's DSL to make
|
2
|
+
# it easier to deal with PostgreSQL row-valued/composite types.
|
3
|
+
#
|
4
|
+
# To load the extension:
|
5
|
+
#
|
6
|
+
# Sequel.extension :pg_row_ops
|
7
|
+
#
|
8
|
+
# The most common usage is passing an expression to Sequel.pg_row_op:
|
9
|
+
#
|
10
|
+
# r = Sequel.pg_row_op(:row_column)
|
11
|
+
#
|
12
|
+
# If you have also loaded the pg_row extension, you can use
|
13
|
+
# Sequel.pg_row as well:
|
14
|
+
#
|
15
|
+
# r = Sequel.pg_row(:row_column)
|
16
|
+
#
|
17
|
+
# Also, on most Sequel expression objects, you can call the pg_row
|
18
|
+
# method:
|
19
|
+
#
|
20
|
+
# r = Sequel.expr(:row_column).pg_row
|
21
|
+
#
|
22
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
23
|
+
# you can also call Symbol#pg_row:
|
24
|
+
#
|
25
|
+
# r = :row_column.pg_row
|
26
|
+
#
|
27
|
+
# There's only fairly basic support currently. You can use the [] method to access
|
28
|
+
# a member of the composite type:
|
29
|
+
#
|
30
|
+
# r[:a] # (row_column).a
|
31
|
+
#
|
32
|
+
# This can be chained:
|
33
|
+
#
|
34
|
+
# r[:a][:b] # ((row_column).a).b
|
35
|
+
#
|
36
|
+
# If you've loaded the pg_array_ops extension, you there is also support for composite
|
37
|
+
# types that include arrays, or arrays of composite types:
|
38
|
+
#
|
39
|
+
# r[1][:a] # (row_column[1]).a
|
40
|
+
# r[:a][1] # (row_column).a[1]
|
41
|
+
#
|
42
|
+
# The only other support is the splat method:
|
43
|
+
#
|
44
|
+
# r.splat # (row_column.*)
|
45
|
+
#
|
46
|
+
# The splat method is necessary if you are trying to reference a table's type when the
|
47
|
+
# table has the same name as one of it's columns. For example:
|
48
|
+
#
|
49
|
+
# DB.create_table(:a){Integer :a; Integer :b}
|
50
|
+
#
|
51
|
+
# Let's say you want to reference the composite type for the table:
|
52
|
+
#
|
53
|
+
# a = Sequel.pg_row_op(:a)
|
54
|
+
# DB[:a].select(a[:b]) # SELECT (a).b FROM a
|
55
|
+
#
|
56
|
+
# Unfortunately, that doesn't work, as it references the integer column, not the table.
|
57
|
+
# The splat method works around this:
|
58
|
+
#
|
59
|
+
# DB[:a].select(a.splat[:b]) # SELECT (a.*).b FROM a
|
60
|
+
#
|
61
|
+
# Splat also takes an argument which is used for casting. This is necessary if you
|
62
|
+
# want to return the composite type itself, instead of the columns in the composite
|
63
|
+
# type. For example:
|
64
|
+
#
|
65
|
+
# DB[:a].select(a.splat).first # SELECT (a.*) FROM a
|
66
|
+
# # => {:a=>1, :b=>2}
|
67
|
+
#
|
68
|
+
# By casting the expression, you can get a composite type returned:
|
69
|
+
#
|
70
|
+
# DB[:a].select(a.splat).first # SELECT (a.*)::a FROM a
|
71
|
+
# # => {:a=>"(1,2)"} # or {:a=>{:a=>1, :b=>2}} if the "a" type has been registered
|
72
|
+
# # with the the pg_row extension
|
73
|
+
#
|
74
|
+
# This feature is mostly useful for a different way to graph tables:
|
75
|
+
#
|
76
|
+
# DB[:a].join(:b, :id=>:b_id).select(Sequel.pg_row_op(:a).splat(:a),
|
77
|
+
# Sequel.pg_row_op(:b).splat(:b))
|
78
|
+
# # SELECT (a.*)::a, (b.*)::b FROM a INNER JOIN b ON (b.id = a.b_id)
|
79
|
+
# # => {:a=>{:id=>1, :b_id=>2}, :b=>{:id=>2}}
|
80
|
+
|
81
|
+
module Sequel
|
82
|
+
module Postgres
|
83
|
+
# This class represents a composite type expression reference.
|
84
|
+
class PGRowOp < SQL::PlaceholderLiteralString
|
85
|
+
OPEN = '('.freeze
|
86
|
+
CLOSE_DOT = ').'.freeze
|
87
|
+
CLOSE_STAR = '.*)'.freeze
|
88
|
+
CLOSE_STAR_CAST = '.*)::'.freeze
|
89
|
+
EMPTY = "".freeze
|
90
|
+
ROW = [OPEN, CLOSE_STAR].freeze
|
91
|
+
ROW_CAST = [OPEN, CLOSE_STAR_CAST].freeze
|
92
|
+
QUALIFY = [OPEN, CLOSE_DOT].freeze
|
93
|
+
WRAP = [EMPTY].freeze
|
94
|
+
|
95
|
+
# Wrap the expression in a PGRowOp, without changing the
|
96
|
+
# SQL it would use.
|
97
|
+
def self.wrap(expr)
|
98
|
+
PGRowOp.new(WRAP, [expr])
|
99
|
+
end
|
100
|
+
|
101
|
+
# Access a member of the composite type if given a
|
102
|
+
# symbol or an SQL::Identifier. For all other access,
|
103
|
+
# assuming the pg_array_ops extension is loaded and
|
104
|
+
# that it represents an array access. In either
|
105
|
+
# case, return a PgRowOp so that access can be cascaded.
|
106
|
+
def [](member)
|
107
|
+
case member
|
108
|
+
when Symbol, SQL::Identifier
|
109
|
+
PGRowOp.new(QUALIFY, [self, member])
|
110
|
+
else
|
111
|
+
PGRowOp.wrap(Sequel.pg_array_op(self)[member])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Use the (identifier.*) syntax to indicate that this
|
116
|
+
# expression represents the composite type of one
|
117
|
+
# of the tables being referenced, if it has the same
|
118
|
+
# name as one of the columns. If the cast_to argument
|
119
|
+
# is given, also cast the expression to that type
|
120
|
+
# (which should be a symbol representing the composite type).
|
121
|
+
# This is used if you want to return whole table row as a
|
122
|
+
# composite type.
|
123
|
+
def splat(cast_to=nil)
|
124
|
+
if args.length > 1
|
125
|
+
raise Error, 'cannot splat a PGRowOp with multiple arguments'
|
126
|
+
end
|
127
|
+
|
128
|
+
if cast_to
|
129
|
+
PGRowOp.new(ROW_CAST, args + [cast_to])
|
130
|
+
else
|
131
|
+
PGRowOp.new(ROW, args)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module ExpressionMethods
|
136
|
+
# Return a PGRowOp wrapping the receiver.
|
137
|
+
def pg_row
|
138
|
+
Sequel.pg_row_op(self)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
module SQL::Builders
|
145
|
+
# Return a PGRowOp wrapping the given expression.
|
146
|
+
def pg_row_op(expr)
|
147
|
+
Postgres::PGRowOp.wrap(expr)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class SQL::GenericExpression
|
152
|
+
include Sequel::Postgres::PGRowOp::ExpressionMethods
|
153
|
+
end
|
154
|
+
|
155
|
+
class LiteralString
|
156
|
+
include Sequel::Postgres::PGRowOp::ExpressionMethods
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
if Sequel.core_extensions?
|
161
|
+
class Symbol
|
162
|
+
include Sequel::Postgres::PGRowOp::ExpressionMethods
|
163
|
+
end
|
164
|
+
end
|
@@ -20,13 +20,13 @@ module Sequel
|
|
20
20
|
#
|
21
21
|
# dataset = DB[:items].query do
|
22
22
|
# select :x, :y, :z
|
23
|
-
# filter{
|
24
|
-
#
|
23
|
+
# filter{(x > 1) & (y > 2)}
|
24
|
+
# reverse :z
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
# Which is the same as:
|
28
28
|
#
|
29
|
-
# dataset = DB[:items].select(:x, :y, :z).filter{
|
29
|
+
# dataset = DB[:items].select(:x, :y, :z).filter{(x > 1) & (y > 2)}.reverse(:z)
|
30
30
|
#
|
31
31
|
# Note that inside a call to query, you cannot call each, insert, update,
|
32
32
|
# or delete (or any method that calls those), or Sequel will raise an
|
@@ -97,14 +97,13 @@ END_MIG
|
|
97
97
|
|
98
98
|
private
|
99
99
|
|
100
|
-
# If a database default exists and can't be converted,
|
101
|
-
# method modified
|
102
|
-
# :same_db option is used.
|
100
|
+
# If a database default exists and can't be converted, and we are dumping with :same_db,
|
101
|
+
# return a string with the inspect method modified a literal string is created if the code is evaled.
|
103
102
|
def column_schema_to_ruby_default_fallback(default, options)
|
104
103
|
if default.is_a?(String) && options[:same_db] && use_column_schema_to_ruby_default_fallback?
|
105
|
-
default = default.
|
104
|
+
default = default.dup
|
106
105
|
def default.inspect
|
107
|
-
"#{super}
|
106
|
+
"Sequel::LiteralString.new(#{super})"
|
108
107
|
end
|
109
108
|
default
|
110
109
|
end
|
@@ -137,7 +136,7 @@ END_MIG
|
|
137
136
|
gen.foreign_key(name, table, col_opts)
|
138
137
|
else
|
139
138
|
gen.column(name, type, col_opts)
|
140
|
-
if
|
139
|
+
if [Integer, Bignum, Float].include?(type) && schema[:db_type] =~ / unsigned\z/io
|
141
140
|
Sequel.extension :eval_inspect
|
142
141
|
gen.check(Sequel::SQL::Identifier.new(name) >= 0)
|
143
142
|
end
|
@@ -162,7 +161,7 @@ END_MIG
|
|
162
161
|
{:type =>schema[:type] == :boolean ? TrueClass : Integer}
|
163
162
|
when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/o
|
164
163
|
{:type=>Bignum}
|
165
|
-
when /\A(?:real|float|double(?: precision)?)\z/o
|
164
|
+
when /\A(?:real|float|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/o
|
166
165
|
{:type=>Float}
|
167
166
|
when 'boolean'
|
168
167
|
{:type=>TrueClass}
|
@@ -371,7 +370,7 @@ END_MIG
|
|
371
370
|
[sorted_tables, skipped_foreign_keys]
|
372
371
|
end
|
373
372
|
|
374
|
-
# Don't use
|
373
|
+
# Don't use a literal string fallback on MySQL, since the defaults it uses aren't
|
375
374
|
# valid literal SQL values.
|
376
375
|
def use_column_schema_to_ruby_default_fallback?
|
377
376
|
database_type != :mysql
|
@@ -24,7 +24,7 @@ module Sequel
|
|
24
24
|
# In this case, the code will currently use unqualified column names for all columns
|
25
25
|
# the dataset returns, except for the columns given.
|
26
26
|
# * This dataset has an existing explicit selection containing an item that returns
|
27
|
-
# multiple database columns (e.g. :table.*, 'column1, column2'
|
27
|
+
# multiple database columns (e.g. Sequel.expr(:table).*, Sequel.lit('column1, column2')). In this case,
|
28
28
|
# the behavior is undefined and this method should not be used.
|
29
29
|
#
|
30
30
|
# There may be other cases where this method does not work correctly, use it with caution.
|
data/lib/sequel/model/base.rb
CHANGED
@@ -872,6 +872,7 @@ module Sequel
|
|
872
872
|
# Artist.new(:name=>'Bob').values # => {:name=>'Bob'}
|
873
873
|
# Artist[1].values # => {:id=>1, :name=>'Jim', ...}
|
874
874
|
attr_reader :values
|
875
|
+
alias to_hash values
|
875
876
|
|
876
877
|
# Creates new instance and passes the given values to set.
|
877
878
|
# If a block is given, yield the instance to the block unless
|
data/lib/sequel/no_core_ext.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
::SEQUEL_NO_CORE_EXTENSIONS = true
|
1
|
+
::SEQUEL_NO_CORE_EXTENSIONS = true unless defined?(::SEQUEL_NO_CORE_EXTENSIONS)
|
2
2
|
require 'sequel'
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The pg_row plugin allows you to use Sequel::Model classes as composite type
|
4
|
+
# classes, via the pg_row extension. So if you have an address table:
|
5
|
+
#
|
6
|
+
# DB.create_table(:address) do
|
7
|
+
# String :street
|
8
|
+
# String :city
|
9
|
+
# String :zip
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# and a company table with an address:
|
13
|
+
#
|
14
|
+
# DB.create_table(:company) do
|
15
|
+
# String :name
|
16
|
+
# address :address
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# You can create a Sequel::Model for the address table, and load the plugin,
|
20
|
+
# which registers the row type:
|
21
|
+
#
|
22
|
+
# class Address < Sequel::Model(:address)
|
23
|
+
# plugin :pg_row
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Then when you select from the company table (even using a plain dataset),
|
27
|
+
# it will return address values as instances of Address:
|
28
|
+
#
|
29
|
+
# DB[:company].first
|
30
|
+
# # => {:name=>'MS', :address=>
|
31
|
+
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345')}
|
32
|
+
#
|
33
|
+
# If you want a lot of your models to be used as row types, you can load the
|
34
|
+
# plugin into Sequel::Model itself:
|
35
|
+
#
|
36
|
+
# Sequel::Model.plugin :pg_row
|
37
|
+
#
|
38
|
+
# And then call register_row_type in the class
|
39
|
+
#
|
40
|
+
# Address.register_row_type
|
41
|
+
#
|
42
|
+
# Note that automatic conversion only works with the native postgres adapter.
|
43
|
+
# For other adapters that connect to PostgreSQL, you need to call the conversion
|
44
|
+
# proc manually.
|
45
|
+
#
|
46
|
+
# In addition to returning row-valued/composite types as instances of Sequel::Model,
|
47
|
+
# this also lets you use model instances in datasets when inserting, updating, and
|
48
|
+
# filtering:
|
49
|
+
#
|
50
|
+
# DB[:company].insert(:name=>'MS', :address=>
|
51
|
+
# Address.load(:street=>'123 Foo St', :city=>'Bar Town', :zip=>'12345'))
|
52
|
+
module PgRow
|
53
|
+
# When loading the extension, make sure the database has the pg_row extension
|
54
|
+
# loaded, load the custom database extensions, and automatically register the
|
55
|
+
# row type if the model has a dataset.
|
56
|
+
def self.configure(model)
|
57
|
+
model.db.extension(:pg_row)
|
58
|
+
model.db.extend(DatabaseMethods)
|
59
|
+
model.register_row_type if model.instance_variable_get(:@dataset)
|
60
|
+
end
|
61
|
+
|
62
|
+
module DatabaseMethods
|
63
|
+
ESCAPE_RE = /("|\\)/.freeze
|
64
|
+
ESCAPE_REPLACEMENT = '\\\\\1'.freeze
|
65
|
+
COMMA = ','
|
66
|
+
|
67
|
+
# Handle Sequel::Model instances in bound variables.
|
68
|
+
def bound_variable_arg(arg, conn)
|
69
|
+
case arg
|
70
|
+
when Sequel::Model
|
71
|
+
"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# If a Sequel::Model instance is given, return it as-is
|
78
|
+
# instead of attempting to convert it.
|
79
|
+
def row_type(db_type, v)
|
80
|
+
if v.is_a?(Sequel::Model)
|
81
|
+
v
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Handle Sequel::Model instances in bound variable arrays.
|
90
|
+
def bound_variable_array(arg)
|
91
|
+
case arg
|
92
|
+
when Sequel::Model
|
93
|
+
"\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
94
|
+
else
|
95
|
+
super
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module ClassMethods
|
101
|
+
# Register the model's row type with the database.
|
102
|
+
def register_row_type
|
103
|
+
db.register_row_type(model.table_name, :converter=>self, :typecaster=>method(:new))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
module InstanceMethods
|
108
|
+
ROW = 'ROW'.freeze
|
109
|
+
CAST = '::'.freeze
|
110
|
+
|
111
|
+
# Literalize the model instance and append it to the sql.
|
112
|
+
def sql_literal_append(ds, sql)
|
113
|
+
sql << ROW
|
114
|
+
ds.literal_append(sql, values.values_at(*columns))
|
115
|
+
sql << CAST
|
116
|
+
ds.quote_schema_table_append(sql, model.table_name)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
|
4
|
+
# using the do, swift, or jdbc adapter, Sequel doesn't have complete
|
5
|
+
# control over typecasting, and may return columns as strings instead of how
|
6
|
+
# the native postgres adapter would typecast them. This is mostly needed for
|
7
|
+
# the additional support that the pg_* extensions add for advanced PostgreSQL
|
8
|
+
# types such as arrays.
|
9
|
+
#
|
10
|
+
# This plugin modifies Model#set_values to do the same conversion that the
|
11
|
+
# native postgres adapter would do for all columns given. You can either
|
12
|
+
# specify the columns to typecast on load in the plugin call itself, or
|
13
|
+
# afterwards using add_pg_typecast_on_load_columns:
|
14
|
+
#
|
15
|
+
# # aliases => text[] column
|
16
|
+
# # config => hstore column
|
17
|
+
#
|
18
|
+
# Album.plugin :pg_typecast_on_load, :aliases, :config
|
19
|
+
# # or:
|
20
|
+
# Album.plugin :pg_typecast_on_load
|
21
|
+
# Album.add_pg_typecast_on_load_columns :aliases, :config
|
22
|
+
#
|
23
|
+
# This plugin only handles values that the adapter returns as strings. If
|
24
|
+
# the adapter returns a value other than a string for a column, that value
|
25
|
+
# will be used directly without typecasting.
|
26
|
+
module PgTypecastOnLoad
|
27
|
+
# Call add_pg_typecast_on_load_columns on the passed column arguments.
|
28
|
+
def self.configure(model, *columns)
|
29
|
+
model.instance_eval do
|
30
|
+
@pg_typecast_on_load_columns ||= []
|
31
|
+
add_pg_typecast_on_load_columns(*columns)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# The columns to typecast on load for this model.
|
37
|
+
attr_reader :pg_typecast_on_load_columns
|
38
|
+
|
39
|
+
# Add additional columns to typecast on load for this model.
|
40
|
+
def add_pg_typecast_on_load_columns(*columns)
|
41
|
+
@pg_typecast_on_load_columns.concat(columns)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Give the subclass a copy of the columns to typecast on load.
|
45
|
+
def inherited(subclass)
|
46
|
+
super
|
47
|
+
subclass.instance_variable_set(:@pg_typecast_on_load_columns, pg_typecast_on_load_columns.dup)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module InstanceMethods
|
52
|
+
# Lookup the conversion proc for the column's oid in the Database
|
53
|
+
# object, and use it to convert the value.
|
54
|
+
def set_values(values)
|
55
|
+
model.pg_typecast_on_load_columns.each do |c|
|
56
|
+
if (v = values[c]).is_a?(String) && (oid = db_schema[c][:oid])
|
57
|
+
values[c] = db.conversion_procs[oid].call(v)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -40,6 +40,37 @@ module Sequel
|
|
40
40
|
# changing the values of the Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS hash. You
|
41
41
|
# change change the default options on a per model basis
|
42
42
|
# by overriding a private instance method default_validation_helpers_options.
|
43
|
+
#
|
44
|
+
# By changing the default options, you can setup internationalization of the
|
45
|
+
# error messages. For example, you would modify the default options:
|
46
|
+
#
|
47
|
+
# Sequel::Plugins::ValidationHelpers::DEFAULT_OPTIONS.merge!(
|
48
|
+
# :exact_length=>{:message=>lambda{|exact| I18n.t("errors.exact_length", :exact => exact)}},
|
49
|
+
# :integer=>{:message=>lambda{I18n.t("errors.integer")}},
|
50
|
+
# ...
|
51
|
+
# )
|
52
|
+
#
|
53
|
+
# and then use something like this in your yaml translation file:
|
54
|
+
#
|
55
|
+
# en:
|
56
|
+
# errors:
|
57
|
+
# exact_length: "is not %{exact} characters"
|
58
|
+
# integer: "is not a number"
|
59
|
+
#
|
60
|
+
# Note that if you want to support internationalization of Errors#full_messages,
|
61
|
+
# you need to override the method. Here's an example:
|
62
|
+
#
|
63
|
+
# class Sequel::Model::Errors
|
64
|
+
# ATTRIBUTE_JOINER = I18n.t('errors.joiner').freeze
|
65
|
+
# def full_messages
|
66
|
+
# inject([]) do |m, kv|
|
67
|
+
# att, errors = *kv
|
68
|
+
# att.is_a?(Array) ? Array(att).map!{|v| I18n.t("attributes.#{v}")} : att = I18n.t("attributes.#{att}")
|
69
|
+
# errors.each {|e| m << (e.is_a?(LiteralString) ? e : "#{Array(att).join(ATTRIBUTE_JOINER)} #{e}")}
|
70
|
+
# m
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# end
|
43
74
|
module ValidationHelpers
|
44
75
|
# Default validation options used by Sequel. Can be modified to change the error
|
45
76
|
# messages for all models (e.g. for internationalization), or to set certain
|