sequel 3.0.0 → 3.1.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 (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