Skip to content

Commit

Permalink
activerecord: CollectionProxy#build can take Hash-like object
Browse files Browse the repository at this point in the history
The current definition of `#build`, `#craete` and `#create!` methods of
`ActiveRecord::Associations::CollectionProxy#build` take a Hash object.

But they can also take Hash-like object.  Usually, Rails apps pass an
instance of `ActionController::Parameters`.  The technique is well known
as "Mass Assignment".

Internally, they expects the object should have `#each_pair` method.
Therefore, this changes the signature of these methods to take a
Hash-like object instead of Hash.

refs: https://github.com/rails/rails/blob/v7.0.0/activemodel/lib/active_model/attribute_assignment.rb#L29
  • Loading branch information
tk0miya committed Jun 11, 2024
1 parent 44f40f2 commit 9915d72
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 4 deletions.
9 changes: 8 additions & 1 deletion gems/activerecord/6.0/_test/activerecord-generated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class User < ActiveRecord::Base
enum status: { active: 0, inactive: 1 }, _suffix: true
enum role: { admin: 0, user: 1 }, _prefix: :user_role

has_many :articles

validates :name, presence: true, if: -> { something }
validates :age, presence: true, if: ->(user) { user.something }

Expand All @@ -12,7 +14,12 @@ def something
end
end

_user = User.new
class Article < ActiveRecord::Base
end

user = User.new
user.articles.build(Hash.new)
user.articles.build(ActionController::Parameters.new)

User.eager_load(:address, friends: [:address, :followers])
User.includes(:address, :friends).to_a
Expand Down
17 changes: 17 additions & 0 deletions gems/activerecord/6.0/_test/activerecord-generated.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@ class User < ActiveRecord::Base
class ActiveRecord_Relation < ActiveRecord::Relation
end

class ActiveRecord_Associations_CollectionProxy < ::ActiveRecord::Associations::CollectionProxy
include ActiveRecord::Relation::Methods[User, Integer]
end

extend ActiveRecord::Base::ClassMethods[User, ActiveRecord_Relation, Integer]

def articles: () -> Article::ActiveRecord_Associations_CollectionProxy

def something: () -> untyped
end

class Article < ActiveRecord::Base
class ActiveRecord_Relation < ActiveRecord::Relation
end

class ActiveRecord_Associations_CollectionProxy < ::ActiveRecord::Associations::CollectionProxy
include ActiveRecord::Relation::Methods[Article, Integer]
end

extend ActiveRecord::Base::ClassMethods[Article, ActiveRecord_Relation, Integer]
end
2 changes: 2 additions & 0 deletions gems/activerecord/6.0/_test/metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
additional_gems:
- actionpack
13 changes: 13 additions & 0 deletions gems/activerecord/6.0/_test/steep_expectations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
- file: activerecord-generated.rb
diagnostics:
- range:
start:
line: 1
character: 6
end:
line: 1
character: 10
severity: ERROR
message: Cannot find implementation of method `::User#articles`
code: Ruby::MethodDefinitionMissing
10 changes: 7 additions & 3 deletions gems/activerecord/6.0/activerecord.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,10 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
interface _EachPair
def each_pair: () { ([untyped, untyped]) -> untyped } -> self
end

def initialize: (untyped klass, untyped association) -> untyped

def target: () -> untyped
Expand Down Expand Up @@ -876,7 +880,7 @@ module ActiveRecord
#
# person.pets.size # => 5 # size of the collection
# person.pets.count # => 0 # count from database
def build: (?::Hash[untyped, untyped] attributes) ?{ () -> untyped } -> untyped
def build: (?_EachPair attributes) ?{ () -> untyped } -> untyped

alias new build

Expand Down Expand Up @@ -906,7 +910,7 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
def create: (?::Hash[untyped, untyped] attributes) ?{ () -> untyped } -> untyped
def create: (?_EachPair attributes) ?{ () -> untyped } -> untyped

# Like #create, except that if the record is invalid, raises an exception.
#
Expand All @@ -920,7 +924,7 @@ module ActiveRecord
#
# person.pets.create!(name: nil)
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
def create!: (?::Hash[untyped, untyped] attributes) ?{ () -> untyped } -> untyped
def create!: (?_EachPair attributes) ?{ () -> untyped } -> untyped

# Replaces this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
Expand Down

0 comments on commit 9915d72

Please sign in to comment.