sequel 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG +100 -0
  2. data/README.rdoc +3 -3
  3. data/bin/sequel +102 -19
  4. data/doc/reflection.rdoc +83 -0
  5. data/doc/release_notes/3.1.0.txt +406 -0
  6. data/lib/sequel/adapters/ado.rb +11 -0
  7. data/lib/sequel/adapters/amalgalite.rb +5 -20
  8. data/lib/sequel/adapters/do.rb +44 -36
  9. data/lib/sequel/adapters/firebird.rb +29 -43
  10. data/lib/sequel/adapters/jdbc.rb +17 -27
  11. data/lib/sequel/adapters/mysql.rb +35 -40
  12. data/lib/sequel/adapters/odbc.rb +4 -23
  13. data/lib/sequel/adapters/oracle.rb +22 -19
  14. data/lib/sequel/adapters/postgres.rb +6 -15
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +29 -10
  17. data/lib/sequel/adapters/shared/oracle.rb +6 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +28 -72
  19. data/lib/sequel/adapters/shared/sqlite.rb +5 -3
  20. data/lib/sequel/adapters/sqlite.rb +5 -20
  21. data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
  22. data/lib/sequel/adapters/utils/unsupported.rb +0 -12
  23. data/lib/sequel/core.rb +12 -3
  24. data/lib/sequel/core_sql.rb +1 -8
  25. data/lib/sequel/database.rb +107 -43
  26. data/lib/sequel/database/schema_generator.rb +1 -0
  27. data/lib/sequel/database/schema_methods.rb +38 -4
  28. data/lib/sequel/dataset.rb +6 -0
  29. data/lib/sequel/dataset/convenience.rb +2 -2
  30. data/lib/sequel/dataset/graph.rb +2 -2
  31. data/lib/sequel/dataset/prepared_statements.rb +3 -8
  32. data/lib/sequel/dataset/sql.rb +93 -19
  33. data/lib/sequel/extensions/blank.rb +2 -1
  34. data/lib/sequel/extensions/inflector.rb +4 -3
  35. data/lib/sequel/extensions/migration.rb +13 -2
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pretty_table.rb +4 -0
  38. data/lib/sequel/extensions/query.rb +4 -0
  39. data/lib/sequel/extensions/schema_dumper.rb +100 -24
  40. data/lib/sequel/extensions/string_date_time.rb +3 -4
  41. data/lib/sequel/model.rb +2 -1
  42. data/lib/sequel/model/associations.rb +96 -38
  43. data/lib/sequel/model/base.rb +14 -14
  44. data/lib/sequel/model/plugins.rb +32 -21
  45. data/lib/sequel/plugins/caching.rb +13 -15
  46. data/lib/sequel/plugins/identity_map.rb +107 -0
  47. data/lib/sequel/plugins/lazy_attributes.rb +65 -0
  48. data/lib/sequel/plugins/many_through_many.rb +188 -0
  49. data/lib/sequel/plugins/schema.rb +13 -0
  50. data/lib/sequel/plugins/serialization.rb +53 -37
  51. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  52. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  53. data/lib/sequel/plugins/validation_class_methods.rb +28 -7
  54. data/lib/sequel/plugins/validation_helpers.rb +31 -24
  55. data/lib/sequel/sql.rb +16 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/ado_spec.rb +47 -1
  58. data/spec/adapters/firebird_spec.rb +39 -36
  59. data/spec/adapters/mysql_spec.rb +25 -9
  60. data/spec/adapters/postgres_spec.rb +11 -24
  61. data/spec/core/database_spec.rb +54 -13
  62. data/spec/core/dataset_spec.rb +147 -29
  63. data/spec/core/object_graph_spec.rb +6 -1
  64. data/spec/core/schema_spec.rb +34 -0
  65. data/spec/core/spec_helper.rb +0 -2
  66. data/spec/extensions/caching_spec.rb +7 -0
  67. data/spec/extensions/identity_map_spec.rb +158 -0
  68. data/spec/extensions/lazy_attributes_spec.rb +113 -0
  69. data/spec/extensions/many_through_many_spec.rb +813 -0
  70. data/spec/extensions/migration_spec.rb +4 -4
  71. data/spec/extensions/schema_dumper_spec.rb +114 -13
  72. data/spec/extensions/schema_spec.rb +19 -3
  73. data/spec/extensions/serialization_spec.rb +28 -0
  74. data/spec/extensions/single_table_inheritance_spec.rb +25 -1
  75. data/spec/extensions/spec_helper.rb +2 -7
  76. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  77. data/spec/extensions/validation_class_methods_spec.rb +10 -5
  78. data/spec/integration/dataset_test.rb +39 -6
  79. data/spec/integration/eager_loader_test.rb +7 -7
  80. data/spec/integration/spec_helper.rb +0 -1
  81. data/spec/integration/transaction_test.rb +28 -1
  82. data/spec/model/association_reflection_spec.rb +29 -3
  83. data/spec/model/associations_spec.rb +1 -0
  84. data/spec/model/eager_loading_spec.rb +70 -1
  85. data/spec/model/plugins_spec.rb +236 -50
  86. data/spec/model/spec_helper.rb +0 -2
  87. metadata +18 -5
@@ -223,6 +223,7 @@ module Sequel
223
223
  @operations << {:op => :add_constraint, :name => name, :type => :check, :check => block || args}
224
224
  end
225
225
 
226
+ # Add a unique constraint to the given column(s)
226
227
  def add_unique_constraint(columns, opts = {})
227
228
  @operations << {:op => :add_constraint, :type => :unique, :columns => Array(columns)}.merge(opts)
228
229
  end
@@ -16,9 +16,18 @@ module Sequel
16
16
  # DB.add_index :posts, :title
17
17
  # DB.add_index :posts, [:author, :title], :unique => true
18
18
  #
19
+ #
20
+ # Options:
21
+ # * :ignore_errors - Ignore any DatabaseErrors that are raised
22
+ #
19
23
  # See alter_table.
20
- def add_index(table, *args)
21
- alter_table(table) {add_index(*args)}
24
+ def add_index(table, columns, options={})
25
+ e = options[:ignore_errors]
26
+ begin
27
+ alter_table(table){add_index(columns, options)}
28
+ rescue DatabaseError
29
+ raise unless e
30
+ end
22
31
  end
23
32
 
24
33
  # Alters the given table with the specified block. Example:
@@ -55,13 +64,14 @@ module Sequel
55
64
  #
56
65
  # Options:
57
66
  # * :temp - Create the table as a temporary table.
67
+ # * :ignore_index_errors - Ignore any errors when creating indexes.
58
68
  #
59
69
  # See Schema::Generator.
60
70
  def create_table(name, options={}, &block)
61
71
  options = {:generator=>options} if options.is_a?(Schema::Generator)
62
72
  generator = options[:generator] || Schema::Generator.new(self, &block)
63
- execute_ddl(create_table_sql(name, generator, options))
64
- index_sql_list(name, generator.indexes).each{|sql| execute_ddl(sql)}
73
+ create_table_from_generator(name, generator, options)
74
+ create_table_indexes_from_generator(name, generator, options)
65
75
  end
66
76
 
67
77
  # Forcibly creates a table, attempting to drop it unconditionally (and catching any errors), then creating it.
@@ -70,6 +80,11 @@ module Sequel
70
80
  create_table(name, options, &block)
71
81
  end
72
82
 
83
+ # Creates the table unless the table already exists
84
+ def create_table?(name, options={}, &block)
85
+ create_table(name, options, &block) unless table_exists?(name)
86
+ end
87
+
73
88
  # Creates a view, replacing it if it already exists:
74
89
  #
75
90
  # DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
@@ -165,5 +180,24 @@ module Sequel
165
180
  def set_column_type(table, *args)
166
181
  alter_table(table) {set_column_type(*args)}
167
182
  end
183
+
184
+ private
185
+
186
+ # Execute the create table statements using the generator.
187
+ def create_table_from_generator(name, generator, options)
188
+ execute_ddl(create_table_sql(name, generator, options))
189
+ end
190
+
191
+ # Execute the create index statements using the generator.
192
+ def create_table_indexes_from_generator(name, generator, options)
193
+ e = options[:ignore_index_errors]
194
+ index_sql_list(name, generator.indexes).each do |sql|
195
+ begin
196
+ execute_ddl(sql)
197
+ rescue DatabaseError
198
+ raise unless e
199
+ end
200
+ end
201
+ end
168
202
  end
169
203
  end
@@ -259,6 +259,12 @@ module Sequel
259
259
  !(@opts.collect{|k,v| k unless v.nil?}.compact & opts).empty?
260
260
  end
261
261
 
262
+ # Whether this dataset is a simple SELECT * FROM table.
263
+ def simple_select_all?
264
+ o = @opts.reject{|k,v| v.nil?}
265
+ o.length == 1 && o[:from] && o[:from].length == 1
266
+ end
267
+
262
268
  private
263
269
 
264
270
  # Set the server to use to :default unless it is already set in the passed opts
@@ -11,7 +11,7 @@ module Sequel
11
11
  #
12
12
  # ds[:id=>1] => {:id=1}
13
13
  def [](*conditions)
14
- raise(Error, ARRAY_ACCESS_ERROR_MSG) if (conditions.length == 1 and conditions.is_a?(Integer)) or conditions.length == 0
14
+ raise(Error, ARRAY_ACCESS_ERROR_MSG) if (conditions.length == 1 and conditions.first.is_a?(Integer)) or conditions.length == 0
15
15
  first(*conditions)
16
16
  end
17
17
 
@@ -109,7 +109,7 @@ module Sequel
109
109
  # # this will commit every 50 records
110
110
  # dataset.import([:x, :y], [[1, 2], [3, 4], ...], :slice => 50)
111
111
  def import(columns, values, opts={})
112
- return @db.transaction{execute_dui("INSERT INTO #{quote_schema_table(@opts[:from].first)} (#{identifier_list(columns)}) VALUES #{literal(values)}")} if values.is_a?(Dataset)
112
+ return @db.transaction{execute_dui("#{insert_sql_base}#{quote_schema_table(@opts[:from].first)} (#{identifier_list(columns)}) VALUES #{literal(values)}")} if values.is_a?(Dataset)
113
113
 
114
114
  return if values.empty?
115
115
  raise(Error, IMPORT_ERROR_MSG) if columns.empty?
@@ -69,7 +69,7 @@ module Sequel
69
69
  raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
70
70
 
71
71
  # Join the table early in order to avoid cloning the dataset twice
72
- ds = join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
72
+ ds = join_table(options[:join_type] || :left_outer, dataset.simple_select_all? ? table : dataset, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
73
73
  opts = ds.opts
74
74
 
75
75
  # Whether to include the table in the result set
@@ -167,7 +167,7 @@ module Sequel
167
167
  # #set_graph_aliases.
168
168
  def add_graph_aliases(graph_aliases)
169
169
  ds = select_more(*graph_alias_columns(graph_aliases))
170
- ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || {}).merge(graph_aliases)
170
+ ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || ds.opts[:graph][:column_aliases] || {}).merge(graph_aliases)
171
171
  ds
172
172
  end
173
173
 
@@ -90,14 +90,9 @@ module Sequel
90
90
  # Changes the values of symbols if they start with $ and
91
91
  # prepared_args is present. If so, they are considered placeholders,
92
92
  # and they are substituted using prepared_arg.
93
- def literal(v)
94
- case v
95
- when Symbol
96
- if match = PLACEHOLDER_RE.match(v.to_s) and @prepared_args
97
- super(prepared_arg(match[1].to_sym))
98
- else
99
- super
100
- end
93
+ def literal_symbol(v)
94
+ if match = PLACEHOLDER_RE.match(v.to_s) and @prepared_args
95
+ literal(prepared_arg(match[1].to_sym))
101
96
  else
102
97
  super
103
98
  end
@@ -7,10 +7,12 @@ module Sequel
7
7
  COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
8
8
  COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
9
9
  COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
10
+ INSERT_SQL_BASE="INSERT INTO ".freeze
10
11
  IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
11
12
  IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
12
13
  N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
13
14
  NULL = "NULL".freeze
15
+ QUALIFY_KEYS = [:select, :where, :having, :order, :group]
14
16
  QUESTION_MARK = '?'.freeze
15
17
  STOCK_COUNT_OPTS = {:select => [SQL::AliasedExpression.new(LiteralString.new("COUNT(*)").freeze, :count)], :order => nil}.freeze
16
18
  SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having compounds order limit'.freeze
@@ -317,7 +319,7 @@ module Sequel
317
319
  if values.empty?
318
320
  insert_default_values_sql
319
321
  else
320
- "INSERT INTO #{from} VALUES #{literal(values)}"
322
+ "#{insert_sql_base}#{from} VALUES #{literal(values)}"
321
323
  end
322
324
  when Hash
323
325
  values = @opts[:defaults].merge(values) if @opts[:defaults]
@@ -330,10 +332,10 @@ module Sequel
330
332
  fl << literal(String === k ? k.to_sym : k)
331
333
  vl << literal(v)
332
334
  end
333
- "INSERT INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
335
+ "#{insert_sql_base}#{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
334
336
  end
335
337
  when Dataset
336
- "INSERT INTO #{from} #{literal(values)}"
338
+ "#{insert_sql_base}#{from} #{literal(values)}"
337
339
  end
338
340
  end
339
341
 
@@ -532,7 +534,7 @@ module Sequel
532
534
  # This method should be overridden by descendants if the support
533
535
  # inserting multiple records in a single SQL statement.
534
536
  def multi_insert_sql(columns, values)
535
- s = "INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES "
537
+ s = "#{insert_sql_base}#{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES "
536
538
  values.map{|r| s + literal(r)}
537
539
  end
538
540
 
@@ -595,6 +597,30 @@ module Sequel
595
597
  [qcr.table, qcr.column].map{|x| [SQL::QualifiedIdentifier, SQL::Identifier, Symbol].any?{|c| x.is_a?(c)} ? literal(x) : quote_identifier(x)}.join('.')
596
598
  end
597
599
 
600
+ # Return a copy of the dataset with unqualified identifiers in the
601
+ # SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
602
+ # given table. If no columns are currently selected, select all
603
+ # columns of the given table.
604
+ def qualify_to(table)
605
+ o = @opts
606
+ return clone if o[:sql]
607
+ h = {}
608
+ (o.keys & QUALIFY_KEYS).each do |k|
609
+ h[k] = qualified_expression(o[k], table)
610
+ end
611
+ h[:select] = [SQL::ColumnAll.new(table)] if !o[:select] || o[:select].empty?
612
+ clone(h)
613
+ end
614
+
615
+ # Qualify the dataset to its current first source. This is useful
616
+ # if you have unqualified identifiers in the query that all refer to
617
+ # the first source, and you want to join to another table which
618
+ # has columns with the same name as columns in the current dataset.
619
+ # See qualify_to.
620
+ def qualify_to_first_source
621
+ qualify_to(first_source)
622
+ end
623
+
598
624
  # Adds quoting to identifiers (columns and tables). If identifiers are not
599
625
  # being quoted, returns name as a string. If identifiers are being quoted
600
626
  # quote the name with quoted_identifier.
@@ -654,7 +680,11 @@ module Sequel
654
680
  # dataset.select{|o| o.a, o.sum(:b)} # SELECT a, sum(b) FROM items
655
681
  def select(*columns, &block)
656
682
  columns += Array(virtual_row_block_call(block)) if block
657
- clone(:select => columns)
683
+ m = []
684
+ columns.map do |i|
685
+ i.is_a?(Hash) ? m.concat(i.map{|k, v| SQL::AliasedExpression.new(k,v)}) : m << i
686
+ end
687
+ clone(:select => m)
658
688
  end
659
689
 
660
690
  # Returns a copy of the dataset selecting the wildcard.
@@ -690,7 +720,7 @@ module Sequel
690
720
 
691
721
  # SQL fragment for specifying subscripts (SQL arrays)
692
722
  def subscript_sql(s)
693
- "#{literal(s.f)}[#{s.sub.join(COMMA_SEPARATOR)}]"
723
+ "#{literal(s.f)}[#{expression_list(s.sub)}]"
694
724
  end
695
725
 
696
726
  # Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
@@ -778,6 +808,12 @@ module Sequel
778
808
 
779
809
  protected
780
810
 
811
+ # Return a from_self dataset if an order or limit is specified, so it works as expected
812
+ # with UNION, EXCEPT, and INTERSECT clauses.
813
+ def compound_from_self
814
+ (@opts[:limit] || @opts[:order]) ? from_self : self
815
+ end
816
+
781
817
  # Returns a table reference for use in the FROM clause. Returns an SQL subquery
782
818
  # frgament with an optional table alias.
783
819
  def to_table_reference(table_alias=nil)
@@ -803,21 +839,14 @@ module Sequel
803
839
  # Converts an array of column names into a comma seperated string of
804
840
  # column names. If the array is empty, a wildcard (*) is returned.
805
841
  def column_list(columns)
806
- if columns.nil? || columns.empty?
807
- WILDCARD
808
- else
809
- m = columns.map do |i|
810
- i.is_a?(Hash) ? i.map{|k, v| as_sql(literal(k), v)} : literal(i)
811
- end
812
- m.join(COMMA_SEPARATOR)
813
- end
842
+ (columns.nil? || columns.empty?) ? WILDCARD : expression_list(columns)
814
843
  end
815
844
 
816
845
  # Add the dataset to the list of compounds
817
846
  def compound_clone(type, dataset, all)
818
- clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset, all]])
847
+ compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, all]]).from_self
819
848
  end
820
-
849
+
821
850
  # Converts an array of expressions into a comma separated string of
822
851
  # expressions.
823
852
  def expression_list(columns)
@@ -863,9 +892,14 @@ module Sequel
863
892
  columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
864
893
  end
865
894
 
895
+ # SQL statement for the beginning of an INSERT statement
896
+ def insert_sql_base
897
+ INSERT_SQL_BASE
898
+ end
899
+
866
900
  # SQL statement for formatting an insert statement with default values
867
901
  def insert_default_values_sql
868
- "INSERT INTO #{source_list(@opts[:from])} DEFAULT VALUES"
902
+ "#{insert_sql_base}#{source_list(@opts[:from])} DEFAULT VALUES"
869
903
  end
870
904
 
871
905
  # Inverts the given order by breaking it into a list of column references
@@ -1003,6 +1037,47 @@ module Sequel
1003
1037
  column
1004
1038
  end
1005
1039
  end
1040
+
1041
+ # Qualify the given expression e to the given table.
1042
+ def qualified_expression(e, table)
1043
+ case e
1044
+ when Symbol
1045
+ t, column, aliaz = split_symbol(e)
1046
+ if t
1047
+ e
1048
+ elsif aliaz
1049
+ SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(table, SQL::Identifier.new(column)), aliaz)
1050
+ else
1051
+ SQL::QualifiedIdentifier.new(table, e)
1052
+ end
1053
+ when Array
1054
+ e.map{|a| qualified_expression(a, table)}
1055
+ when Hash
1056
+ h = {}
1057
+ e.each{|k,v| h[qualified_expression(k, table)] = qualified_expression(v, table)}
1058
+ h
1059
+ when SQL::Identifier
1060
+ SQL::QualifiedIdentifier.new(table, e)
1061
+ when SQL::OrderedExpression
1062
+ SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending)
1063
+ when SQL::AliasedExpression
1064
+ SQL::AliasedExpression.new(qualified_expression(e.expression, table), e.aliaz)
1065
+ when SQL::CaseExpression
1066
+ SQL::CaseExpression.new(qualified_expression(e.conditions, table), qualified_expression(e.default, table), qualified_expression(e.expression, table))
1067
+ when SQL::Cast
1068
+ SQL::Cast.new(qualified_expression(e.expr, table), e.type)
1069
+ when SQL::Function
1070
+ SQL::Function.new(e.f, *qualified_expression(e.args, table))
1071
+ when SQL::ComplexExpression
1072
+ SQL::ComplexExpression.new(e.op, *qualified_expression(e.args, table))
1073
+ when SQL::SQLArray
1074
+ SQL::SQLArray.new(qualified_expression(e.array, table))
1075
+ when SQL::Subscript
1076
+ SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
1077
+ else
1078
+ e
1079
+ end
1080
+ end
1006
1081
 
1007
1082
  # The order of methods to call to build the SELECT SQL statement
1008
1083
  def select_clause_order
@@ -1028,8 +1103,7 @@ module Sequel
1028
1103
  return unless @opts[:compounds]
1029
1104
  @opts[:compounds].each do |type, dataset, all|
1030
1105
  compound_sql = subselect_sql(dataset)
1031
- compound_sql = "SELECT * FROM (#{compound_sql})" if dataset.opts[:compounds]
1032
- sql.replace("#{sql} #{type.to_s.upcase}#{' ALL' if all} #{compound_sql}")
1106
+ sql << " #{type.to_s.upcase}#{' ALL' if all} #{compound_sql}"
1033
1107
  end
1034
1108
  end
1035
1109
 
@@ -1,3 +1,5 @@
1
+ # The blank extension adds the blank? method to all objects (e.g. Object#blank?).
2
+
1
3
  class FalseClass
2
4
  # false is always blank
3
5
  def blank?
@@ -5,7 +7,6 @@ class FalseClass
5
7
  end
6
8
  end
7
9
 
8
- # Helpers from Metaid and a bit more
9
10
  class Object
10
11
  # Objects are blank if they respond true to empty?
11
12
  def blank?
@@ -1,6 +1,7 @@
1
- # Add inflection methods to String, which allows the easy transformation of
2
- # words from singular to plural,class names to table names, modularized class
3
- # names to ones without, and class names to foreign keys.
1
+ # The inflector extension adds inflection instance methods to String, which allows the easy transformation of
2
+ # words from singular to plural, class names to table names, modularized class
3
+ # names to ones without, and class names to foreign keys. It exists for
4
+ # backwards compatibility to legacy Sequel code.
4
5
 
5
6
  class String
6
7
  # This module acts as a singleton returned/yielded by String.inflections,
@@ -1,3 +1,7 @@
1
+ # Adds the Sequel::Migration and Sequel::Migrator classes, which allow
2
+ # the user to easily group schema changes and migrate the database
3
+ # to a newer version or revert to a previous version.
4
+
1
5
  module Sequel
2
6
  # The Migration class describes a database migration that can be reversed.
3
7
  # The migration looks very similar to ActiveRecord (Rails) migrations, e.g.:
@@ -128,6 +132,7 @@ module Sequel
128
132
  # Sequel::Migrator.apply(DB, '.', 5, 1)
129
133
  module Migrator
130
134
  MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/.freeze
135
+ MIGRATION_SPLITTER = '_'.freeze
131
136
 
132
137
  # Migrates the supplied database in the specified directory from the
133
138
  # current version to the target version. If no current version is
@@ -162,7 +167,7 @@ module Sequel
162
167
  # Returns the latest version available in the specified directory.
163
168
  def self.latest_migration_version(directory)
164
169
  l = migration_files(directory).last
165
- l ? File.basename(l).to_i : nil
170
+ l ? migration_version_from_file(File.basename(l)) : nil
166
171
  end
167
172
 
168
173
  # Returns a list of migration classes filtered for the migration range and
@@ -190,7 +195,7 @@ module Sequel
190
195
  def self.migration_files(directory, range = nil)
191
196
  files = []
192
197
  Dir.new(directory).each do |file|
193
- files[file.to_i] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
198
+ files[migration_version_from_file(file)] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
194
199
  end
195
200
  filtered = range ? files[range] : files
196
201
  filtered ? filtered.compact : []
@@ -208,5 +213,11 @@ module Sequel
208
213
  dataset = schema_info_dataset(db)
209
214
  dataset.send(dataset.first ? :update : :<<, :version => version)
210
215
  end
216
+
217
+ # Return the integer migration version based on the filename.
218
+ def self.migration_version_from_file(filename)
219
+ filename.split(MIGRATION_SPLITTER, 2).first.to_i
220
+ end
221
+ private_class_method :migration_version_from_file
211
222
  end
212
223
  end
@@ -1,3 +1,7 @@
1
+ # The pagination extension adds the Sequel::Dataset#paginate and #each_page methods,
2
+ # which return paginated (limited and offset) datasets with some helpful methods
3
+ # that make creating a paginated display easier.
4
+
1
5
  module Sequel
2
6
  class Dataset
3
7
  # Returns a paginated dataset. The returned dataset is limited to
@@ -1,3 +1,7 @@
1
+ # The pretty_table extension adds Sequel::Dataset#print and the
2
+ # Sequel::PrettyTable class for creating nice-looking plain-text
3
+ # tables.
4
+
1
5
  module Sequel
2
6
  class Dataset
3
7
  # Pretty prints the records in the dataset as plain-text table.
@@ -1,3 +1,7 @@
1
+ # The query extension adds Sequel::Dataset#query which allows
2
+ # a different way to construct queries instead of the usual
3
+ # method chaining.
4
+
1
5
  module Sequel
2
6
  class Database
3
7
  # Return a dataset modified by the query block