activerecord 5.0.0.beta1.1 → 5.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -15
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +1 -1
  5. data/lib/active_record.rb +2 -1
  6. data/lib/active_record/aggregations.rb +1 -1
  7. data/lib/active_record/associations.rb +3 -0
  8. data/lib/active_record/associations/builder/belongs_to.rb +1 -1
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  11. data/lib/active_record/associations/has_many_through_association.rb +5 -0
  12. data/lib/active_record/associations/join_dependency/join_association.rb +1 -2
  13. data/lib/active_record/associations/preloader/through_association.rb +7 -2
  14. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -7
  15. data/lib/active_record/autosave_association.rb +18 -3
  16. data/lib/active_record/base.rb +0 -3
  17. data/lib/active_record/collection_cache_key.rb +12 -3
  18. data/lib/active_record/connection_adapters/abstract/database_statements.rb +21 -34
  19. data/lib/active_record/connection_adapters/abstract/quoting.rb +8 -4
  20. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +7 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +36 -20
  23. data/lib/active_record/connection_adapters/abstract/transaction.rb +8 -2
  24. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -15
  25. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -198
  26. data/lib/active_record/connection_adapters/column.rb +1 -1
  27. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  28. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  29. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +24 -0
  30. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  31. data/lib/active_record/connection_adapters/mysql2_adapter.rb +15 -34
  32. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +7 -69
  33. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  34. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +4 -0
  35. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  36. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -1
  37. data/lib/active_record/connection_adapters/postgresql/quoting.rb +5 -4
  38. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -7
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -23
  40. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  41. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -31
  42. data/lib/active_record/connection_handling.rb +1 -1
  43. data/lib/active_record/core.rb +1 -1
  44. data/lib/active_record/counter_cache.rb +4 -4
  45. data/lib/active_record/enum.rb +8 -5
  46. data/lib/active_record/errors.rb +6 -1
  47. data/lib/active_record/gem_version.rb +1 -1
  48. data/lib/active_record/inheritance.rb +6 -1
  49. data/lib/active_record/internal_metadata.rb +56 -0
  50. data/lib/active_record/migration.rb +85 -20
  51. data/lib/active_record/migration/compatibility.rb +28 -2
  52. data/lib/active_record/model_schema.rb +25 -1
  53. data/lib/active_record/persistence.rb +11 -10
  54. data/lib/active_record/railtie.rb +6 -3
  55. data/lib/active_record/railties/databases.rake +20 -6
  56. data/lib/active_record/reflection.rb +39 -31
  57. data/lib/active_record/relation.rb +4 -4
  58. data/lib/active_record/relation/batches.rb +26 -41
  59. data/lib/active_record/relation/batches/batch_enumerator.rb +6 -6
  60. data/lib/active_record/relation/finder_methods.rb +35 -13
  61. data/lib/active_record/relation/from_clause.rb +1 -1
  62. data/lib/active_record/relation/merger.rb +3 -0
  63. data/lib/active_record/relation/predicate_builder.rb +19 -1
  64. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -1
  65. data/lib/active_record/relation/query_methods.rb +37 -19
  66. data/lib/active_record/relation/record_fetch_warning.rb +4 -6
  67. data/lib/active_record/relation/where_clause.rb +1 -1
  68. data/lib/active_record/relation/where_clause_factory.rb +1 -0
  69. data/lib/active_record/sanitization.rb +1 -1
  70. data/lib/active_record/schema.rb +3 -0
  71. data/lib/active_record/schema_dumper.rb +1 -1
  72. data/lib/active_record/schema_migration.rb +5 -14
  73. data/lib/active_record/scoping.rb +17 -11
  74. data/lib/active_record/scoping/default.rb +2 -2
  75. data/lib/active_record/tasks/database_tasks.rb +18 -0
  76. data/lib/active_record/timestamp.rb +5 -1
  77. data/lib/active_record/transactions.rb +3 -3
  78. data/lib/active_record/validations/uniqueness.rb +6 -3
  79. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -0
  80. data/lib/rails/generators/active_record/model/model_generator.rb +7 -1
  81. metadata +14 -7
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def bigint?
33
- /bigint/ === sql_type
33
+ /\Abigint\b/ === sql_type
34
34
  end
35
35
 
36
36
  # Returns the human name of the column name.
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module MySQL
4
+ class Column < ConnectionAdapters::Column # :nodoc:
5
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
6
+
7
+ def initialize(*)
8
+ super
9
+ assert_valid_default
10
+ extract_default
11
+ end
12
+
13
+ def has_default?
14
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
15
+ super
16
+ end
17
+
18
+ def blob_or_text_column?
19
+ /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text
20
+ end
21
+
22
+ def unsigned?
23
+ /\bunsigned\z/ === sql_type
24
+ end
25
+
26
+ def case_sensitive?
27
+ collation && collation !~ /_ci\z/
28
+ end
29
+
30
+ def auto_increment?
31
+ extra == 'auto_increment'
32
+ end
33
+
34
+ private
35
+
36
+ def extract_default
37
+ if blob_or_text_column?
38
+ @default = null || strict ? nil : ''
39
+ end
40
+ end
41
+
42
+ def assert_valid_default
43
+ if blob_or_text_column? && default.present?
44
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,70 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module MySQL
4
+ class ExplainPrettyPrinter # :nodoc:
5
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
6
+ # MySQL shell:
7
+ #
8
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
9
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
10
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
11
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
12
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
13
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
14
+ # 2 rows in set (0.00 sec)
15
+ #
16
+ # This is an exercise in Ruby hyperrealism :).
17
+ def pp(result, elapsed)
18
+ widths = compute_column_widths(result)
19
+ separator = build_separator(widths)
20
+
21
+ pp = []
22
+
23
+ pp << separator
24
+ pp << build_cells(result.columns, widths)
25
+ pp << separator
26
+
27
+ result.rows.each do |row|
28
+ pp << build_cells(row, widths)
29
+ end
30
+
31
+ pp << separator
32
+ pp << build_footer(result.rows.length, elapsed)
33
+
34
+ pp.join("\n") + "\n"
35
+ end
36
+
37
+ private
38
+
39
+ def compute_column_widths(result)
40
+ [].tap do |widths|
41
+ result.columns.each_with_index do |column, i|
42
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
43
+ widths << cells_in_column.map(&:length).max
44
+ end
45
+ end
46
+ end
47
+
48
+ def build_separator(widths)
49
+ padding = 1
50
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
51
+ end
52
+
53
+ def build_cells(items, widths)
54
+ cells = []
55
+ items.each_with_index do |item, i|
56
+ item = 'NULL' if item.nil?
57
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
58
+ cells << item.to_s.send(justifier, widths[i])
59
+ end
60
+ '| ' + cells.join(' | ') + ' |'
61
+ end
62
+
63
+ def build_footer(nrows, elapsed)
64
+ rows_label = nrows == 1 ? 'row' : 'rows'
65
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -11,6 +11,30 @@ module ActiveRecord
11
11
  args.each { |name| column(name, :blob, options) }
12
12
  end
13
13
 
14
+ def tinyblob(*args, **options)
15
+ args.each { |name| column(name, :tinyblob, options) }
16
+ end
17
+
18
+ def mediumblob(*args, **options)
19
+ args.each { |name| column(name, :mediumblob, options) }
20
+ end
21
+
22
+ def longblob(*args, **options)
23
+ args.each { |name| column(name, :longblob, options) }
24
+ end
25
+
26
+ def tinytext(*args, **options)
27
+ args.each { |name| column(name, :tinytext, options) }
28
+ end
29
+
30
+ def mediumtext(*args, **options)
31
+ args.each { |name| column(name, :mediumtext, options) }
32
+ end
33
+
34
+ def longtext(*args, **options)
35
+ args.each { |name| column(name, :longtext, options) }
36
+ end
37
+
14
38
  def json(*args, **options)
15
39
  args.each { |name| column(name, :json, options) }
16
40
  end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module MySQL
4
+ class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
5
+ attr_reader :extra, :strict
6
+
7
+ def initialize(type_metadata, extra: "", strict: false)
8
+ super(type_metadata)
9
+ @type_metadata = type_metadata
10
+ @extra = extra
11
+ @strict = strict
12
+ end
13
+
14
+ def ==(other)
15
+ other.is_a?(MySQL::TypeMetadata) &&
16
+ attributes_for_hash == other.attributes_for_hash
17
+ end
18
+ alias eql? ==
19
+
20
+ def hash
21
+ attributes_for_hash.hash
22
+ end
23
+
24
+ protected
25
+
26
+ def attributes_for_hash
27
+ [self.class, @type_metadata, extra, strict]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -11,8 +11,13 @@ module ActiveRecord
11
11
 
12
12
  config[:username] = 'root' if config[:username].nil?
13
13
  config[:flags] ||= 0
14
+
14
15
  if Mysql2::Client.const_defined? :FOUND_ROWS
15
- config[:flags] |= Mysql2::Client::FOUND_ROWS
16
+ if config[:flags].kind_of? Array
17
+ config[:flags].push "FOUND_ROWS".freeze
18
+ else
19
+ config[:flags] |= Mysql2::Client::FOUND_ROWS
20
+ end
16
21
  end
17
22
 
18
23
  client = Mysql2::Client.new(config)
@@ -94,33 +99,15 @@ module ActiveRecord
94
99
  # DATABASE STATEMENTS ======================================
95
100
  #++
96
101
 
97
- # FIXME: re-enable the following once a "better" query_cache solution is in core
98
- #
99
- # The overrides below perform much better than the originals in AbstractAdapter
100
- # because we're able to take advantage of mysql2's lazy-loading capabilities
101
- #
102
- # # Returns a record hash with the column names as keys and column values
103
- # # as values.
104
- # def select_one(sql, name = nil)
105
- # result = execute(sql, name)
106
- # result.each(as: :hash) do |r|
107
- # return r
108
- # end
109
- # end
110
- #
111
- # # Returns a single value from a record
112
- # def select_value(sql, name = nil)
113
- # result = execute(sql, name)
114
- # if first = result.first
115
- # first.first
116
- # end
117
- # end
118
- #
119
- # # Returns an array of the values of the first column in a select:
120
- # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
121
- # def select_values(sql, name = nil)
122
- # execute(sql, name).map { |row| row.first }
123
- # end
102
+ # Returns a record hash with the column names as keys and column values
103
+ # as values.
104
+ def select_one(arel, name = nil, binds = [])
105
+ arel, binds = binds_from_relation(arel, binds)
106
+ execute(to_sql(arel, binds), name).each(as: :hash) do |row|
107
+ @connection.next_result while @connection.more_results?
108
+ return row
109
+ end
110
+ end
124
111
 
125
112
  # Returns an array of arrays containing the field values.
126
113
  # Order is the same as that returned by +columns+.
@@ -149,12 +136,6 @@ module ActiveRecord
149
136
 
150
137
  alias exec_without_stmt exec_query
151
138
 
152
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
153
- super
154
- id_value || @connection.last_id
155
- end
156
- alias :create :insert_sql
157
-
158
139
  def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
159
140
  execute to_sql(sql, binds), name
160
141
  end
@@ -4,44 +4,7 @@ module ActiveRecord
4
4
  module DatabaseStatements
5
5
  def explain(arel, binds = [])
6
6
  sql = "EXPLAIN #{to_sql(arel, binds)}"
7
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
8
- end
9
-
10
- class ExplainPrettyPrinter # :nodoc:
11
- # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
12
- # PostgreSQL shell:
13
- #
14
- # QUERY PLAN
15
- # ------------------------------------------------------------------------------
16
- # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
17
- # Join Filter: (posts.user_id = users.id)
18
- # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
19
- # Index Cond: (id = 1)
20
- # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
21
- # Filter: (posts.user_id = 1)
22
- # (6 rows)
23
- #
24
- def pp(result)
25
- header = result.columns.first
26
- lines = result.rows.map(&:first)
27
-
28
- # We add 2 because there's one char of padding at both sides, note
29
- # the extra hyphens in the example above.
30
- width = [header, *lines].map(&:length).max + 2
31
-
32
- pp = []
33
-
34
- pp << header.center(width).rstrip
35
- pp << '-' * width
36
-
37
- pp += lines.map {|line| " #{line}"}
38
-
39
- nrows = result.rows.length
40
- rows_label = nrows == 1 ? 'row' : 'rows'
41
- pp << "(#{nrows} #{rows_label})"
42
-
43
- pp.join("\n") + "\n"
44
- end
7
+ PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
45
8
  end
46
9
 
47
10
  def select_value(arel, name = nil, binds = [])
@@ -52,8 +15,8 @@ module ActiveRecord
52
15
  end
53
16
  end
54
17
 
55
- def select_values(arel, name = nil)
56
- arel, binds = binds_from_relation arel, []
18
+ def select_values(arel, name = nil, binds = [])
19
+ arel, binds = binds_from_relation arel, binds
57
20
  sql = to_sql(arel, binds)
58
21
  execute_and_clear(sql, name, binds) do |result|
59
22
  if result.nfields > 0
@@ -72,28 +35,6 @@ module ActiveRecord
72
35
  end
73
36
  end
74
37
 
75
- # Executes an INSERT query and returns the new record's ID
76
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
77
- unless pk
78
- # Extract the table from the insert sql. Yuck.
79
- table_ref = extract_table_ref_from_insert_sql(sql)
80
- pk = primary_key(table_ref) if table_ref
81
- end
82
-
83
- if pk && use_insert_returning?
84
- select_value("#{sql} RETURNING #{quote_column_name(pk)}")
85
- elsif pk
86
- super
87
- last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
88
- else
89
- super
90
- end
91
- end
92
-
93
- def create
94
- super.insert
95
- end
96
-
97
38
  # The internal PostgreSQL identifier of the money data type.
98
39
  MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
99
40
  # The internal PostgreSQL identifier of the BYTEA data type.
@@ -150,6 +91,8 @@ module ActiveRecord
150
91
 
151
92
  # Executes an SQL statement, returning a PGresult object on success
152
93
  # or raising a PGError exception otherwise.
94
+ # Note: the PGresult object is manually memory managed; if you don't
95
+ # need it specifically, you many want consider the exec_query wrapper.
153
96
  def execute(sql, name = nil)
154
97
  log(sql, name) do
155
98
  @connection.async_exec(sql)
@@ -174,7 +117,7 @@ module ActiveRecord
174
117
  end
175
118
  alias :exec_update :exec_delete
176
119
 
177
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
120
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
178
121
  unless pk
179
122
  # Extract the table from the insert sql. Yuck.
180
123
  table_ref = extract_table_ref_from_insert_sql(sql)
@@ -185,7 +128,7 @@ module ActiveRecord
185
128
  sql = "#{sql} RETURNING #{quote_column_name(pk)}"
186
129
  end
187
130
 
188
- [sql, binds]
131
+ super
189
132
  end
190
133
 
191
134
  def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
@@ -202,11 +145,6 @@ module ActiveRecord
202
145
  end
203
146
  end
204
147
 
205
- # Executes an UPDATE query and returns the number of affected tuples.
206
- def update_sql(sql, name = nil)
207
- super.cmd_tuples
208
- end
209
-
210
148
  # Begins a transaction.
211
149
  def begin_db_transaction
212
150
  execute "BEGIN"
@@ -0,0 +1,42 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostgreSQL
4
+ class ExplainPrettyPrinter # :nodoc:
5
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
6
+ # PostgreSQL shell:
7
+ #
8
+ # QUERY PLAN
9
+ # ------------------------------------------------------------------------------
10
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
11
+ # Join Filter: (posts.user_id = users.id)
12
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
13
+ # Index Cond: (id = 1)
14
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
15
+ # Filter: (posts.user_id = 1)
16
+ # (6 rows)
17
+ #
18
+ def pp(result)
19
+ header = result.columns.first
20
+ lines = result.rows.map(&:first)
21
+
22
+ # We add 2 because there's one char of padding at both sides, note
23
+ # the extra hyphens in the example above.
24
+ width = [header, *lines].map(&:length).max + 2
25
+
26
+ pp = []
27
+
28
+ pp << header.center(width).rstrip
29
+ pp << '-' * width
30
+
31
+ pp += lines.map {|line| " #{line}"}
32
+
33
+ nrows = result.rows.length
34
+ rows_label = nrows == 1 ? 'row' : 'rows'
35
+ pp << "(#{nrows} #{rows_label})"
36
+
37
+ pp.join("\n") + "\n"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -50,6 +50,10 @@ module ActiveRecord
50
50
  "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]"
51
51
  end
52
52
 
53
+ def map(value, &block)
54
+ value.map(&block)
55
+ end
56
+
53
57
  private
54
58
 
55
59
  def type_cast_array(value, method)
@@ -3,8 +3,6 @@ module ActiveRecord
3
3
  module PostgreSQL
4
4
  module OID # :nodoc:
5
5
  class Money < Type::Decimal # :nodoc:
6
- class_attribute :precision
7
-
8
6
  def type
9
7
  :money
10
8
  end