From d6841934219fb0c3c1db99f100e4faf628296940 Mon Sep 17 00:00:00 2001 From: Koji NAKAMURA Date: Mon, 15 Jun 2026 16:43:20 +0900 Subject: [PATCH] fix(activerecord): refine create/create! RBS signatures and update tests - Allow passing a block to `create` / `create!` . - Add overloads for `attributes` as an Array of hashes and array return types. --- .../7.2/_test/activerecord-7.2.rb | 14 +++++++ gems/activerecord/7.2/activerecord-7.2.rbs | 37 +++++++++++++++++++ .../7.2/activerecord-generated.rbs | 33 ----------------- gems/activerecord/7.2/activerecord.rbs | 2 - .../8.0/_test/activerecord-8.0.rb | 14 +++++++ gems/activerecord/8.0/activerecord-8.0.rbs | 37 +++++++++++++++++++ .../8.0/activerecord-generated.rbs | 33 ----------------- gems/activerecord/8.0/activerecord.rbs | 2 - 8 files changed, 102 insertions(+), 70 deletions(-) diff --git a/gems/activerecord/7.2/_test/activerecord-7.2.rb b/gems/activerecord/7.2/_test/activerecord-7.2.rb index 3c87076a..bd677b76 100644 --- a/gems/activerecord/7.2/_test/activerecord-7.2.rb +++ b/gems/activerecord/7.2/_test/activerecord-7.2.rb @@ -91,4 +91,18 @@ class Article < ApplicationRecord User.order(id: :asc).reorder(id: :desc) User.all.table_name + + user = User.create(name: 'James') + user.articles + User.create(name: 'James') { |user| user.articles } + users = User.create([{name: 'James'}, {name: 'John'}]) + users.first.articles + User.create([{name: 'James'}, {name: 'John'}]) { |user| user.articles } + + user = User.create!(name: 'James') + user.articles + User.create!(name: 'James') { |user| user.articles } + users = User.create!([{name: 'James'}, {name: 'John'}]) + users.first.articles + User.create!([{name: 'James'}, {name: 'John'}]) { |user| user.articles } end diff --git a/gems/activerecord/7.2/activerecord-7.2.rbs b/gems/activerecord/7.2/activerecord-7.2.rbs index dc84b3ca..8ad1168c 100644 --- a/gems/activerecord/7.2/activerecord-7.2.rbs +++ b/gems/activerecord/7.2/activerecord-7.2.rbs @@ -156,6 +156,43 @@ module ActiveRecord module Persistence def previously_persisted?: () -> bool + + module ClassMethods + # Creates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the + # attributes on the objects that are to be created. + # + # ==== Examples + # # Create a single new object + # User.create(first_name: 'Jamie') + # + # # Create an Array of new objects + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) + # + # # Create a single object and pass it into a block to set other attributes. + # User.create(first_name: 'Jamie') do |u| + # u.is_admin = false + # end + # + # # Creating an Array of new objects using a block, where the block is executed for each object: + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| + # u.is_admin = false + # end + def create: (Array[untyped] attributes) ?{ (instance) -> void } -> Array[instance] + | (?untyped? attributes) ?{ (instance) -> void } -> instance + + # Creates an object (or multiple objects) and saves it to the database, + # if validations pass. Raises a RecordInvalid error if validations fail, + # unlike Base#create. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. + # These describe which attributes to be created on the object, or + # multiple objects when given an Array of Hashes. + def create!: (Array[untyped] attributes) ?{ (instance) -> void } -> Array[instance] + | (?untyped? attributes) ?{ (instance) -> void } -> instance + end end class InsertAll diff --git a/gems/activerecord/7.2/activerecord-generated.rbs b/gems/activerecord/7.2/activerecord-generated.rbs index a3854de2..29d4ab10 100644 --- a/gems/activerecord/7.2/activerecord-generated.rbs +++ b/gems/activerecord/7.2/activerecord-generated.rbs @@ -16121,39 +16121,6 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - # Creates an object (or multiple objects) and saves it to the database, if validations pass. - # The resulting object is returned whether the object was saved successfully to the database or not. - # - # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the - # attributes on the objects that are to be created. - # - # ==== Examples - # # Create a single new object - # User.create(first_name: 'Jamie') - # - # # Create an Array of new objects - # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) - # - # # Create a single object and pass it into a block to set other attributes. - # User.create(first_name: 'Jamie') do |u| - # u.is_admin = false - # end - # - # # Creating an Array of new objects using a block, where the block is executed for each object: - # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| - # u.is_admin = false - # end - def create: (?untyped? attributes) ?{ () -> untyped } -> untyped - - # Creates an object (or multiple objects) and saves it to the database, - # if validations pass. Raises a RecordInvalid error if validations fail, - # unlike Base#create. - # - # The +attributes+ parameter can be either a Hash or an Array of Hashes. - # These describe which attributes to be created on the object, or - # multiple objects when given an Array of Hashes. - def create!: (?untyped? attributes) ?{ () -> untyped } -> untyped - # Given an attributes hash, +instantiate+ returns a new instance of # the appropriate class. Accepts only keys as strings. # diff --git a/gems/activerecord/7.2/activerecord.rbs b/gems/activerecord/7.2/activerecord.rbs index f26648ce..78d7a2f8 100644 --- a/gems/activerecord/7.2/activerecord.rbs +++ b/gems/activerecord/7.2/activerecord.rbs @@ -49,8 +49,6 @@ module ActiveRecord def self.has_one: (Symbol, ?untyped, **untyped) -> void def self.has_and_belongs_to_many: (untyped name, ?untyped? scope, **untyped options) ?{ () -> untyped } -> untyped def self.transaction: [T] (?requires_new: boolish, ?isolation: (:read_uncommitted | :read_committed | :repeatable_read | :serializable)?, ?joinable: boolish) { () -> T } -> T - def self.create: (**untyped) -> instance - def self.create!: (**untyped) -> instance def self.validate: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void def self.validates: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void diff --git a/gems/activerecord/8.0/_test/activerecord-8.0.rb b/gems/activerecord/8.0/_test/activerecord-8.0.rb index 3c87076a..bd677b76 100644 --- a/gems/activerecord/8.0/_test/activerecord-8.0.rb +++ b/gems/activerecord/8.0/_test/activerecord-8.0.rb @@ -91,4 +91,18 @@ class Article < ApplicationRecord User.order(id: :asc).reorder(id: :desc) User.all.table_name + + user = User.create(name: 'James') + user.articles + User.create(name: 'James') { |user| user.articles } + users = User.create([{name: 'James'}, {name: 'John'}]) + users.first.articles + User.create([{name: 'James'}, {name: 'John'}]) { |user| user.articles } + + user = User.create!(name: 'James') + user.articles + User.create!(name: 'James') { |user| user.articles } + users = User.create!([{name: 'James'}, {name: 'John'}]) + users.first.articles + User.create!([{name: 'James'}, {name: 'John'}]) { |user| user.articles } end diff --git a/gems/activerecord/8.0/activerecord-8.0.rbs b/gems/activerecord/8.0/activerecord-8.0.rbs index dc84b3ca..8ad1168c 100644 --- a/gems/activerecord/8.0/activerecord-8.0.rbs +++ b/gems/activerecord/8.0/activerecord-8.0.rbs @@ -156,6 +156,43 @@ module ActiveRecord module Persistence def previously_persisted?: () -> bool + + module ClassMethods + # Creates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the + # attributes on the objects that are to be created. + # + # ==== Examples + # # Create a single new object + # User.create(first_name: 'Jamie') + # + # # Create an Array of new objects + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) + # + # # Create a single object and pass it into a block to set other attributes. + # User.create(first_name: 'Jamie') do |u| + # u.is_admin = false + # end + # + # # Creating an Array of new objects using a block, where the block is executed for each object: + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| + # u.is_admin = false + # end + def create: (Array[untyped] attributes) ?{ (instance) -> void } -> Array[instance] + | (?untyped? attributes) ?{ (instance) -> void } -> instance + + # Creates an object (or multiple objects) and saves it to the database, + # if validations pass. Raises a RecordInvalid error if validations fail, + # unlike Base#create. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. + # These describe which attributes to be created on the object, or + # multiple objects when given an Array of Hashes. + def create!: (Array[untyped] attributes) ?{ (instance) -> void } -> Array[instance] + | (?untyped? attributes) ?{ (instance) -> void } -> instance + end end class InsertAll diff --git a/gems/activerecord/8.0/activerecord-generated.rbs b/gems/activerecord/8.0/activerecord-generated.rbs index a3854de2..29d4ab10 100644 --- a/gems/activerecord/8.0/activerecord-generated.rbs +++ b/gems/activerecord/8.0/activerecord-generated.rbs @@ -16121,39 +16121,6 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - # Creates an object (or multiple objects) and saves it to the database, if validations pass. - # The resulting object is returned whether the object was saved successfully to the database or not. - # - # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the - # attributes on the objects that are to be created. - # - # ==== Examples - # # Create a single new object - # User.create(first_name: 'Jamie') - # - # # Create an Array of new objects - # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) - # - # # Create a single object and pass it into a block to set other attributes. - # User.create(first_name: 'Jamie') do |u| - # u.is_admin = false - # end - # - # # Creating an Array of new objects using a block, where the block is executed for each object: - # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| - # u.is_admin = false - # end - def create: (?untyped? attributes) ?{ () -> untyped } -> untyped - - # Creates an object (or multiple objects) and saves it to the database, - # if validations pass. Raises a RecordInvalid error if validations fail, - # unlike Base#create. - # - # The +attributes+ parameter can be either a Hash or an Array of Hashes. - # These describe which attributes to be created on the object, or - # multiple objects when given an Array of Hashes. - def create!: (?untyped? attributes) ?{ () -> untyped } -> untyped - # Given an attributes hash, +instantiate+ returns a new instance of # the appropriate class. Accepts only keys as strings. # diff --git a/gems/activerecord/8.0/activerecord.rbs b/gems/activerecord/8.0/activerecord.rbs index 9e560da6..6e3b1448 100644 --- a/gems/activerecord/8.0/activerecord.rbs +++ b/gems/activerecord/8.0/activerecord.rbs @@ -49,8 +49,6 @@ module ActiveRecord def self.has_one: (Symbol, ?untyped, **untyped) -> void def self.has_and_belongs_to_many: (untyped name, ?untyped? scope, **untyped options) ?{ () -> untyped } -> untyped def self.transaction: [T] (?requires_new: boolish, ?isolation: (:read_uncommitted | :read_committed | :repeatable_read | :serializable)?, ?joinable: boolish) { () -> T } -> T - def self.create: (**untyped) -> instance - def self.create!: (**untyped) -> instance def self.validate: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void def self.validates: (*untyped, ?if: conditions[instance], ?unless: conditions[instance], **untyped) -> void