activerecord 7.0.6 → 7.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fc8d9f64d2ef73d31d77f9bb3ab1792606a33e541bd28a08566304fc81d5d07
4
- data.tar.gz: 7e4b61e90c14d831d3b888859ba713dd5cbb35d1554225aec1c483ee8857aee9
3
+ metadata.gz: cbd03f9e73d8cd308ad7eef63e3ece5612be70272833ffaa9f0c9f1251a1a0b6
4
+ data.tar.gz: c3efeab424b1d87a4c977ff53c0f97f60cfa89bd6d37723d24bd682c84f7f52a
5
5
  SHA512:
6
- metadata.gz: faf9806425e9a75ecde61e1b84bc6eec459cd18895684200701394315b5e7bff69487a4baab67482a80baa1f5e2fcf81021781ceb81cba39f7fbc75bfe7200d4
7
- data.tar.gz: b940c37c5f10f000f37ba68eb9be3aa2ae5dabc329a16979587dd1cd75ba464976cefccc437ba9958eb444a35f9303214d7e5f0d06d37b4e45809337977d8829
6
+ metadata.gz: 07caf53afe430ac4a57d208fd7f46cc4430749bc6ee6d8b3666c934e37cde92d3ae2553c3ab0f3c31d18ccedcf5efc89540863ba777c7cea2eefa6e3badad17a
7
+ data.tar.gz: d9fbd502b44cdf8d53b3ff6c75f58f98aec5580bc829be6bd9511da7302ec1483ec747c9871839951d7fb37a0485c44d44de24f3bbab432db2a69d23e8c1565a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,59 @@
1
+ ## Rails 7.0.7 (August 09, 2023) ##
2
+
3
+ * Restores functionality to the missing method when using enums and fixes.
4
+
5
+ *paulreece*
6
+
7
+ * Fix `StatementCache::Substitute` with serialized type.
8
+
9
+ *ywenc*
10
+
11
+ * Fix `:db_runtime` on notification payload when application have multiple databases.
12
+
13
+ *Eileen M. Uchitelle*
14
+
15
+ * Correctly dump check constraints for MySQL 8.0.16+.
16
+
17
+ *Steve Hill*
18
+
19
+ * Fix `ActiveRecord::QueryMethods#in_order_of` to include `nil`s, to match the
20
+ behavior of `Enumerable#in_order_of`.
21
+
22
+ For example, `Post.in_order_of(:title, [nil, "foo"])` will now include posts
23
+ with `nil` titles, the same as `Post.all.to_a.in_order_of(:title, [nil, "foo"])`.
24
+
25
+ *fatkodima*
26
+
27
+ * Revert "Fix autosave associations with validations added on `:base` of the associated objects."
28
+
29
+ This change intended to remove the :base attribute from the message,
30
+ but broke many assumptions which key these errors were stored.
31
+
32
+ *zzak*
33
+
34
+ * Fix `#previously_new_record?` to return true for destroyed records.
35
+
36
+ Before, if a record was created and then destroyed, `#previously_new_record?` would return true.
37
+ Now, any UPDATE or DELETE to a record is considered a change, and will result in `#previously_new_record?`
38
+ returning false.
39
+
40
+ *Adrianna Chang*
41
+
42
+ * Revert breaking changes to `has_one` relationship deleting the old record before the new one is validated.
43
+
44
+ *zzak*
45
+
46
+ * Fix support for Active Record instances being uses in queries.
47
+
48
+ As of `7.0.5`, query arguments were deep duped to avoid mutations impacting
49
+ the query cache, but this had the adverse effect to clearing the primary key when
50
+ the query argument contained an `ActiveRecord::Base` instance.
51
+
52
+ This broke the `noticed` gem.
53
+
54
+ *Jean Boussier*
55
+
56
+
1
57
  ## Rails 7.0.6 (June 29, 2023) ##
2
58
 
3
59
  * Fix autosave associations with validations added on `:base` of the associated objects.
@@ -12,6 +68,10 @@
12
68
 
13
69
  *fatkodima*
14
70
 
71
+ * Fix assignment into an `has_one` relationship deleting the old record before the new one is validated.
72
+
73
+ *Jean Boussier*
74
+
15
75
  * Fix where on association with has_one/has_many polymorphic relations.
16
76
 
17
77
  Before:
@@ -87,10 +87,6 @@ module ActiveRecord
87
87
  replace(record, false)
88
88
  end
89
89
 
90
- def replace_keys(record, force: false)
91
- # Has one association doesn't have foreign keys to replace.
92
- end
93
-
94
90
  def remove_target!(method)
95
91
  case method
96
92
  when :delete
@@ -54,13 +54,11 @@ module ActiveRecord
54
54
  end
55
55
 
56
56
  def _create_record(attributes, raise_error = false, &block)
57
- reflection.klass.transaction do
58
- record = build(attributes, &block)
59
- saved = record.save
60
- replace_keys(record, force: true)
61
- raise RecordInvalid.new(record) if !saved && raise_error
62
- record
63
- end
57
+ record = build_record(attributes, &block)
58
+ saved = record.save
59
+ set_new_record(record)
60
+ raise RecordInvalid.new(record) if !saved && raise_error
61
+ record
64
62
  end
65
63
  end
66
64
  end
@@ -354,15 +354,11 @@ module ActiveRecord
354
354
  end
355
355
 
356
356
  def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
357
- normalized_attribute =
358
- if indexed_attribute
359
- "#{reflection.name}[#{index}]"
360
- else
361
- reflection.name
362
- end
363
-
364
- normalized_attribute = "#{normalized_attribute}.#{attribute}" if attribute != :base
365
- normalized_attribute
357
+ if indexed_attribute
358
+ "#{reflection.name}[#{index}].#{attribute}"
359
+ else
360
+ "#{reflection.name}.#{attribute}"
361
+ end
366
362
  end
367
363
 
368
364
  # Is used as an around_save callback to check while saving a collection
@@ -71,14 +71,6 @@ module ActiveRecord
71
71
  /\A(?:[(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
72
72
  end
73
73
 
74
- def self.quoted_column_names # :nodoc:
75
- @quoted_column_names ||= {}
76
- end
77
-
78
- def self.quoted_table_names # :nodoc:
79
- @quoted_table_names ||= {}
80
- end
81
-
82
74
  def initialize(connection, logger = nil, config = {}) # :nodoc:
83
75
  super()
84
76
 
@@ -665,15 +657,6 @@ module ActiveRecord
665
657
  migration_context.current_version
666
658
  end
667
659
 
668
- def field_ordered_value(column, values) # :nodoc:
669
- node = Arel::Nodes::Case.new(column)
670
- values.each.with_index(1) do |value, order|
671
- node.when(value).then(order)
672
- end
673
-
674
- Arel::Nodes::Ascending.new(node.else(values.length + 1))
675
- end
676
-
677
660
  class << self
678
661
  private
679
662
  def initialize_type_map(m)
@@ -138,11 +138,6 @@ module ActiveRecord
138
138
  true
139
139
  end
140
140
 
141
- def field_ordered_value(column, values) # :nodoc:
142
- field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse.map { |value| Arel::Nodes.build_quoted(value) }])
143
- Arel::Nodes::Descending.new(field)
144
- end
145
-
146
141
  def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
147
142
  query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
148
143
  end
@@ -438,7 +433,8 @@ module ActiveRecord
438
433
  name: row["name"]
439
434
  }
440
435
  expression = row["expression"]
441
- expression = expression[1..-2] unless mariadb? # remove parentheses added by mysql
436
+ expression = expression[1..-2] if expression.start_with?("(") && expression.end_with?(")")
437
+ expression = strip_whitespace_characters(expression)
442
438
  CheckConstraintDefinition.new(table_name, expression, options)
443
439
  end
444
440
  else
@@ -619,6 +615,12 @@ module ActiveRecord
619
615
  end
620
616
 
621
617
  private
618
+ def strip_whitespace_characters(expression)
619
+ expression = expression.gsub(/\\n|\\\\/, "")
620
+ expression = expression.gsub(/\s{2,}/, " ")
621
+ expression
622
+ end
623
+
622
624
  def text_type?(type)
623
625
  TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
624
626
  end
@@ -6,6 +6,9 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
9
12
  def quote_bound_value(value)
10
13
  case value
11
14
  when Rational
@@ -24,11 +27,11 @@ module ActiveRecord
24
27
  end
25
28
 
26
29
  def quote_column_name(name)
27
- self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
30
+ QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
28
31
  end
29
32
 
30
33
  def quote_table_name(name)
31
- self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
34
+ QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
32
35
  end
33
36
 
34
37
  def unquoted_true
@@ -4,6 +4,9 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
+
7
10
  class IntegerOutOf64BitRange < StandardError
8
11
  def initialize(msg)
9
12
  super(msg)
@@ -81,7 +84,7 @@ module ActiveRecord
81
84
  # - "schema.name".table_name
82
85
  # - "schema.name"."table.name"
83
86
  def quote_table_name(name) # :nodoc:
84
- self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
87
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
85
88
  end
86
89
 
87
90
  # Quotes schema names for use in SQL queries.
@@ -95,7 +98,7 @@ module ActiveRecord
95
98
 
96
99
  # Quotes column names for use in SQL queries.
97
100
  def quote_column_name(name) # :nodoc:
98
- self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
101
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(super).freeze
99
102
  end
100
103
 
101
104
  # Quote date/time values for use in SQL input.
@@ -4,6 +4,9 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
6
  module Quoting # :nodoc:
7
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
+
7
10
  def quote_string(s)
8
11
  @connection.class.quote(s)
9
12
  end
@@ -13,11 +16,11 @@ module ActiveRecord
13
16
  end
14
17
 
15
18
  def quote_table_name(name)
16
- self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
19
+ QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "\".\"").freeze
17
20
  end
18
21
 
19
22
  def quote_column_name(name)
20
- self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
23
+ QUOTED_COLUMN_NAMES[name] ||= %Q("#{super.gsub('"', '""')}")
21
24
  end
22
25
 
23
26
  def quoted_time(value)
@@ -84,11 +84,11 @@ module ActiveRecord
84
84
  table_sql = query_value(<<-SQL, "SCHEMA")
85
85
  SELECT sql
86
86
  FROM sqlite_master
87
- WHERE name = #{quote_table_name(table_name)} AND type = 'table'
87
+ WHERE name = #{quote(table_name)} AND type = 'table'
88
88
  UNION ALL
89
89
  SELECT sql
90
90
  FROM sqlite_temp_master
91
- WHERE name = #{quote_table_name(table_name)} AND type = 'table'
91
+ WHERE name = #{quote(table_name)} AND type = 'table'
92
92
  SQL
93
93
 
94
94
  table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
- TINY = 6
12
+ TINY = 7
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -564,7 +564,7 @@ module ActiveRecord
564
564
  end
565
565
 
566
566
  # Returns true if this object was just created -- that is, prior to the last
567
- # save, the object didn't exist in the database and new_record? would have
567
+ # update or delete, the object didn't exist in the database and new_record? would have
568
568
  # returned true.
569
569
  def previously_new_record?
570
570
  @previously_new_record
@@ -663,6 +663,7 @@ module ActiveRecord
663
663
  def delete
664
664
  _delete_row if persisted?
665
665
  @destroyed = true
666
+ @previously_new_record = false
666
667
  freeze
667
668
  end
668
669
 
@@ -682,6 +683,7 @@ module ActiveRecord
682
683
  true
683
684
  end
684
685
  @destroyed = true
686
+ @previously_new_record = false
685
687
  freeze
686
688
  end
687
689
 
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  end
29
29
 
30
30
  def cleanup_view_runtime
31
- if logger && logger.info? && ActiveRecord::Base.connected?
31
+ if logger && logger.info?
32
32
  db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
33
33
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
34
34
  runtime = super
@@ -42,9 +42,8 @@ module ActiveRecord
42
42
 
43
43
  def append_info_to_payload(payload)
44
44
  super
45
- if ActiveRecord::Base.connected?
46
- payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
47
- end
45
+
46
+ payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime
48
47
  end
49
48
  end
50
49
  end
@@ -65,8 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def build_bind_attribute(column_name, value)
68
- type = table.type(column_name)
69
- Relation::QueryAttribute.new(column_name, type.immutable_value(value), type)
68
+ Relation::QueryAttribute.new(column_name, value, table.type(column_name))
70
69
  end
71
70
 
72
71
  def resolve_arel_attribute(table_name, column_name, &block)
@@ -5,6 +5,20 @@ require "active_model/attribute"
5
5
  module ActiveRecord
6
6
  class Relation
7
7
  class QueryAttribute < ActiveModel::Attribute # :nodoc:
8
+ def initialize(...)
9
+ super
10
+
11
+ # The query attribute value may be mutated before we actually "compile" the query.
12
+ # To avoid that if the type uses a serializer we eagerly compute the value for database
13
+ if value_before_type_cast.is_a?(StatementCache::Substitute)
14
+ # we don't need to serialize StatementCache::Substitute
15
+ elsif @type.serialized?
16
+ value_for_database
17
+ elsif @type.mutable? # If the type is simply mutable, we deep_dup it.
18
+ @value_before_type_cast = @value_before_type_cast.deep_dup
19
+ end
20
+ end
21
+
8
22
  def type_cast(value)
9
23
  value
10
24
  end
@@ -35,6 +49,15 @@ module ActiveRecord
35
49
  @_unboundable
36
50
  end
37
51
 
52
+ def ==(other)
53
+ super && value_for_database == other.value_for_database
54
+ end
55
+ alias eql? ==
56
+
57
+ def hash
58
+ [self.class, name, value_for_database, type].hash
59
+ end
60
+
38
61
  private
39
62
  def infinity?(value)
40
63
  value.respond_to?(:infinite?) && value.infinite?
@@ -77,7 +77,7 @@ module ActiveRecord
77
77
  associations.each do |association|
78
78
  reflection = scope_association_reflection(association)
79
79
  @scope.joins!(association)
80
- if @scope.table_name == reflection.table_name
80
+ if reflection.options[:class_name]
81
81
  self.not(association => { reflection.association_primary_key => nil })
82
82
  else
83
83
  self.not(reflection.table_name => { reflection.association_primary_key => nil })
@@ -109,7 +109,7 @@ module ActiveRecord
109
109
  associations.each do |association|
110
110
  reflection = scope_association_reflection(association)
111
111
  @scope.left_outer_joins!(association)
112
- if @scope.table_name == reflection.table_name
112
+ if reflection.options[:class_name]
113
113
  @scope.where!(association => { reflection.association_primary_key => nil })
114
114
  else
115
115
  @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
@@ -444,13 +444,16 @@ module ActiveRecord
444
444
  self
445
445
  end
446
446
 
447
- # Allows to specify an order by a specific set of values. Depending on your
448
- # adapter this will either use a CASE statement or a built-in function.
447
+ # Allows to specify an order by a specific set of values.
449
448
  #
450
449
  # User.in_order_of(:id, [1, 5, 3])
451
450
  # # SELECT "users".* FROM "users"
452
- # # ORDER BY FIELD("users"."id", 1, 5, 3)
453
451
  # # WHERE "users"."id" IN (1, 5, 3)
452
+ # # ORDER BY CASE
453
+ # # WHEN "users"."id" = 1 THEN 1
454
+ # # WHEN "users"."id" = 5 THEN 2
455
+ # # WHEN "users"."id" = 3 THEN 3
456
+ # # END ASC
454
457
  #
455
458
  def in_order_of(column, values)
456
459
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
@@ -462,9 +465,16 @@ module ActiveRecord
462
465
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
463
466
  arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
464
467
 
468
+ where_clause =
469
+ if values.include?(nil)
470
+ arel_column.in(values.compact).or(arel_column.eq(nil))
471
+ else
472
+ arel_column.in(values)
473
+ end
474
+
465
475
  spawn
466
- .order!(connection.field_ordered_value(arel_column, values))
467
- .where!(arel_column.in(values))
476
+ .order!(build_case_for_value_position(arel_column, values))
477
+ .where!(where_clause)
468
478
  end
469
479
 
470
480
  # Replaces any existing order defined on the relation with the specified order.
@@ -1669,6 +1679,15 @@ module ActiveRecord
1669
1679
  end
1670
1680
  end
1671
1681
 
1682
+ def build_case_for_value_position(column, values)
1683
+ node = Arel::Nodes::Case.new
1684
+ values.each.with_index(1) do |value, order|
1685
+ node.when(column.eq(value)).then(order)
1686
+ end
1687
+
1688
+ Arel::Nodes::Ascending.new(node)
1689
+ end
1690
+
1672
1691
  def resolve_arel_attributes(attrs)
1673
1692
  attrs.flat_map do |attr|
1674
1693
  case attr
@@ -336,9 +336,9 @@ module ActiveRecord
336
336
  @_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state
337
337
  end
338
338
 
339
- # Executes +method+ within a transaction and captures its return value as a
340
- # status flag. If the status is true the transaction is committed, otherwise
341
- # a ROLLBACK is issued. In any case the status flag is returned.
339
+ # Executes a block within a transaction and captures its return value as a
340
+ # status flag. If the status is true, the transaction is committed,
341
+ # otherwise a ROLLBACK is issued. In any case, the status flag is returned.
342
342
  #
343
343
  # This method is available within the context of an ActiveRecord::Base
344
344
  # instance.
@@ -55,6 +55,10 @@ module ActiveRecord
55
55
  coder.respond_to?(:object_class) && value.is_a?(coder.object_class)
56
56
  end
57
57
 
58
+ def serialized? # :nodoc:
59
+ true
60
+ end
61
+
58
62
  private
59
63
  def default_value?(value)
60
64
  value == coder.load(nil)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.6
4
+ version: 7.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-29 00:00:00.000000000 Z
11
+ date: 2023-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.6
19
+ version: 7.0.7
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.6
26
+ version: 7.0.7
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 7.0.6
33
+ version: 7.0.7
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 7.0.6
40
+ version: 7.0.7
41
41
  description: Databases on Rails. Build a persistent domain model by mapping database
42
42
  tables to Ruby classes. Strong conventions for associations, validations, aggregations,
43
43
  migrations, and testing come baked-in.
@@ -434,12 +434,12 @@ licenses:
434
434
  - MIT
435
435
  metadata:
436
436
  bug_tracker_uri: https://github.com/rails/rails/issues
437
- changelog_uri: https://github.com/rails/rails/blob/v7.0.6/activerecord/CHANGELOG.md
438
- documentation_uri: https://api.rubyonrails.org/v7.0.6/
437
+ changelog_uri: https://github.com/rails/rails/blob/v7.0.7/activerecord/CHANGELOG.md
438
+ documentation_uri: https://api.rubyonrails.org/v7.0.7/
439
439
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
440
- source_code_uri: https://github.com/rails/rails/tree/v7.0.6/activerecord
440
+ source_code_uri: https://github.com/rails/rails/tree/v7.0.7/activerecord
441
441
  rubygems_mfa_required: 'true'
442
- post_install_message:
442
+ post_install_message:
443
443
  rdoc_options:
444
444
  - "--main"
445
445
  - README.rdoc
@@ -456,8 +456,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
456
456
  - !ruby/object:Gem::Version
457
457
  version: '0'
458
458
  requirements: []
459
- rubygems_version: 3.4.13
460
- signing_key:
459
+ rubygems_version: 3.4.10
460
+ signing_key:
461
461
  specification_version: 4
462
462
  summary: Object-relational mapper framework (part of Rails).
463
463
  test_files: []