sequel 3.37.0 → 3.38.0

Sign up to get free protection for your applications and to get access to all the features.
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