sequel 3.37.0 → 3.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +82 -58
  3. data/Rakefile +6 -5
  4. data/bin/sequel +1 -1
  5. data/doc/active_record.rdoc +67 -52
  6. data/doc/advanced_associations.rdoc +33 -48
  7. data/doc/association_basics.rdoc +41 -51
  8. data/doc/cheat_sheet.rdoc +21 -21
  9. data/doc/core_extensions.rdoc +374 -0
  10. data/doc/dataset_basics.rdoc +5 -5
  11. data/doc/dataset_filtering.rdoc +47 -43
  12. data/doc/mass_assignment.rdoc +1 -1
  13. data/doc/migration.rdoc +4 -5
  14. data/doc/model_hooks.rdoc +3 -3
  15. data/doc/object_model.rdoc +31 -25
  16. data/doc/opening_databases.rdoc +19 -5
  17. data/doc/prepared_statements.rdoc +2 -2
  18. data/doc/querying.rdoc +109 -52
  19. data/doc/reflection.rdoc +6 -6
  20. data/doc/release_notes/3.38.0.txt +234 -0
  21. data/doc/schema_modification.rdoc +22 -13
  22. data/doc/sharding.rdoc +8 -9
  23. data/doc/sql.rdoc +154 -112
  24. data/doc/testing.rdoc +47 -7
  25. data/doc/thread_safety.rdoc +1 -1
  26. data/doc/transactions.rdoc +1 -1
  27. data/doc/validations.rdoc +1 -1
  28. data/doc/virtual_rows.rdoc +29 -43
  29. data/lib/sequel/adapters/do/postgres.rb +1 -4
  30. data/lib/sequel/adapters/jdbc.rb +14 -3
  31. data/lib/sequel/adapters/jdbc/db2.rb +9 -0
  32. data/lib/sequel/adapters/jdbc/derby.rb +41 -4
  33. data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
  34. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
  35. data/lib/sequel/adapters/mock.rb +10 -4
  36. data/lib/sequel/adapters/postgres.rb +1 -28
  37. data/lib/sequel/adapters/shared/mssql.rb +23 -13
  38. data/lib/sequel/adapters/shared/postgres.rb +46 -0
  39. data/lib/sequel/adapters/swift.rb +21 -13
  40. data/lib/sequel/adapters/swift/mysql.rb +1 -0
  41. data/lib/sequel/adapters/swift/postgres.rb +4 -5
  42. data/lib/sequel/adapters/swift/sqlite.rb +2 -1
  43. data/lib/sequel/adapters/tinytds.rb +14 -2
  44. data/lib/sequel/adapters/utils/pg_types.rb +5 -0
  45. data/lib/sequel/core.rb +29 -17
  46. data/lib/sequel/database/query.rb +1 -1
  47. data/lib/sequel/database/schema_generator.rb +3 -0
  48. data/lib/sequel/dataset/actions.rb +5 -6
  49. data/lib/sequel/dataset/query.rb +7 -7
  50. data/lib/sequel/dataset/sql.rb +5 -18
  51. data/lib/sequel/extensions/core_extensions.rb +8 -12
  52. data/lib/sequel/extensions/pg_array.rb +59 -33
  53. data/lib/sequel/extensions/pg_array_ops.rb +32 -4
  54. data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
  55. data/lib/sequel/extensions/pg_hstore.rb +32 -17
  56. data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
  57. data/lib/sequel/extensions/pg_inet.rb +1 -2
  58. data/lib/sequel/extensions/pg_interval.rb +0 -1
  59. data/lib/sequel/extensions/pg_json.rb +41 -23
  60. data/lib/sequel/extensions/pg_range.rb +36 -11
  61. data/lib/sequel/extensions/pg_range_ops.rb +32 -4
  62. data/lib/sequel/extensions/pg_row.rb +572 -0
  63. data/lib/sequel/extensions/pg_row_ops.rb +164 -0
  64. data/lib/sequel/extensions/query.rb +3 -3
  65. data/lib/sequel/extensions/schema_dumper.rb +7 -8
  66. data/lib/sequel/extensions/select_remove.rb +1 -1
  67. data/lib/sequel/model/base.rb +1 -0
  68. data/lib/sequel/no_core_ext.rb +1 -1
  69. data/lib/sequel/plugins/pg_row.rb +121 -0
  70. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +31 -0
  72. data/lib/sequel/sql.rb +64 -44
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/mssql_spec.rb +37 -12
  75. data/spec/adapters/mysql_spec.rb +39 -75
  76. data/spec/adapters/oracle_spec.rb +11 -11
  77. data/spec/adapters/postgres_spec.rb +414 -237
  78. data/spec/adapters/spec_helper.rb +1 -1
  79. data/spec/adapters/sqlite_spec.rb +14 -14
  80. data/spec/core/database_spec.rb +6 -6
  81. data/spec/core/dataset_spec.rb +169 -205
  82. data/spec/core/expression_filters_spec.rb +182 -295
  83. data/spec/core/object_graph_spec.rb +6 -6
  84. data/spec/core/schema_spec.rb +14 -14
  85. data/spec/core/spec_helper.rb +1 -0
  86. data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
  87. data/spec/extensions/columns_introspection_spec.rb +5 -5
  88. data/spec/extensions/hook_class_methods_spec.rb +28 -36
  89. data/spec/extensions/many_through_many_spec.rb +4 -4
  90. data/spec/extensions/pg_array_ops_spec.rb +15 -7
  91. data/spec/extensions/pg_array_spec.rb +81 -48
  92. data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
  93. data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
  94. data/spec/extensions/pg_hstore_spec.rb +66 -65
  95. data/spec/extensions/pg_inet_spec.rb +2 -4
  96. data/spec/extensions/pg_interval_spec.rb +2 -3
  97. data/spec/extensions/pg_json_spec.rb +20 -18
  98. data/spec/extensions/pg_range_ops_spec.rb +11 -4
  99. data/spec/extensions/pg_range_spec.rb +30 -7
  100. data/spec/extensions/pg_row_ops_spec.rb +48 -0
  101. data/spec/extensions/pg_row_plugin_spec.rb +45 -0
  102. data/spec/extensions/pg_row_spec.rb +323 -0
  103. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  104. data/spec/extensions/query_literals_spec.rb +11 -11
  105. data/spec/extensions/query_spec.rb +3 -3
  106. data/spec/extensions/schema_dumper_spec.rb +20 -4
  107. data/spec/extensions/schema_spec.rb +18 -41
  108. data/spec/extensions/select_remove_spec.rb +4 -4
  109. data/spec/extensions/spec_helper.rb +4 -8
  110. data/spec/extensions/to_dot_spec.rb +5 -5
  111. data/spec/extensions/validation_class_methods_spec.rb +28 -16
  112. data/spec/integration/associations_test.rb +20 -20
  113. data/spec/integration/dataset_test.rb +98 -98
  114. data/spec/integration/eager_loader_test.rb +13 -27
  115. data/spec/integration/plugin_test.rb +5 -5
  116. data/spec/integration/prepared_statement_test.rb +22 -13
  117. data/spec/integration/schema_test.rb +28 -18
  118. data/spec/integration/spec_helper.rb +1 -1
  119. data/spec/integration/timezone_test.rb +2 -2
  120. data/spec/integration/type_test.rb +15 -6
  121. data/spec/model/association_reflection_spec.rb +1 -1
  122. data/spec/model/associations_spec.rb +4 -4
  123. data/spec/model/base_spec.rb +5 -5
  124. data/spec/model/eager_loading_spec.rb +15 -15
  125. data/spec/model/model_spec.rb +32 -32
  126. data/spec/model/record_spec.rb +16 -0
  127. data/spec/model/spec_helper.rb +2 -6
  128. data/spec/model/validations_spec.rb +1 -1
  129. 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{|o| (o.x > 1) & (o.y > 2)}
24
- # order :z.desc
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{|o| (o.x > 1) & (o.y > 2)}.order(:z.desc)
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, return the string with the inspect
101
- # method modified so that .lit is always appended after it, only if the
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.to_s
104
+ default = default.dup
106
105
  def default.inspect
107
- "#{super}.lit" # core_sql use
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 (type == Integer || type == Bignum) && schema[:db_type] =~ / unsigned\z/io
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 the "...".lit fallback on MySQL, since the defaults it uses aren't
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'.lit). In this case,
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.
@@ -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
@@ -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