Extracted attributes assingment from ActiveRecord to ActiveModel #10776

Merged
merged 1 commit into from Jan 23, 2015

Conversation

Projects
None yet
@bogdan
Contributor

bogdan commented May 28, 2013

Now you are able to do:

class Feedback
  include ActiveModel::AttributeAssignment
  attr_accessor :email, :subject, :body
end

f = Feedback.new
f.assign_attributes(
  :email => "bogdan@example.com", 
  :subject => "Hello", 
  :body => "The app dont work for me"
)

This is just a proof of concept PR.

There is still some todos:

  • Write tests for ActiveModel::AttributeAssignment
  • Deprecate AR::UnknownAttributeError in flavor of AM one
  • Add change log entry
  • rebase change log entry 2-3 times

I am gonna add them if this will be approved to merge in.

activerecord/lib/active_record/errors.rb
- end
-
- end
+ UnknownAttributeError = ActiveModel::AttributeAssignment::UnknownAttributeError

This comment has been minimized.

@dmathieu

dmathieu May 28, 2013

Contributor

I don't think this is still necessary.

@dmathieu

dmathieu May 28, 2013

Contributor

I don't think this is still necessary.

This comment has been minimized.

@bogdan

bogdan May 28, 2013

Contributor

Existing rails apps could still use this constant to catch this exception and process it manually.

@bogdan

bogdan May 28, 2013

Contributor

Existing rails apps could still use this constant to catch this exception and process it manually.

This comment has been minimized.

@dmathieu

dmathieu May 28, 2013

Contributor

Raising it has to trigger a deprecation then.

@dmathieu

dmathieu May 28, 2013

Contributor

Raising it has to trigger a deprecation then.

This comment has been minimized.

@bogdan

bogdan Feb 13, 2014

Contributor

Do you know how to do that?

@bogdan

bogdan Feb 13, 2014

Contributor

Do you know how to do that?

@dmathieu

This comment has been minimized.

Show comment
Hide comment
@dmathieu

dmathieu May 28, 2013

Contributor

Is there a reason why you didn't extract the tests too ?

Contributor

dmathieu commented May 28, 2013

Is there a reason why you didn't extract the tests too ?

@bogdan

This comment has been minimized.

Show comment
Hide comment
@bogdan

bogdan Sep 25, 2013

Contributor

@dmathieu once I saw someone from core team saying that overlapping between AR and AM tests is good idea. No concrete reason.

Contributor

bogdan commented Sep 25, 2013

@dmathieu once I saw someone from core team saying that overlapping between AR and AM tests is good idea. No concrete reason.

+
+ private
+
+ def _assign_attribute(k, v)

This comment has been minimized.

@robin850

robin850 Sep 28, 2013

Member

Could you please indent the private methods as the contribution guideline states ? Also, I think that this will at least need a changelog entry. Not sure if you should add one in AR changelog as well.

@robin850

robin850 Sep 28, 2013

Member

Could you please indent the private methods as the contribution guideline states ? Also, I think that this will at least need a changelog entry. Not sure if you should add one in AR changelog as well.

This comment has been minimized.

@robin850

robin850 Sep 28, 2013

Member

Nice pull request though, thank you!

@robin850

robin850 Sep 28, 2013

Member

Nice pull request though, thank you!

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Jan 21, 2015

Member

What is the motivation for this extraction?

Member

rafaelfranca commented Jan 21, 2015

What is the motivation for this extraction?

@bogdan

This comment has been minimized.

Show comment
Hide comment
@bogdan

bogdan Jan 21, 2015

Contributor

Using outside of activerecord just like validation and others:

Attributes assignment concept is something I personally use a lot for many types of objects.

Contributor

bogdan commented Jan 21, 2015

Using outside of activerecord just like validation and others:

Attributes assignment concept is something I personally use a lot for many types of objects.

@rafaelfranca

This comment has been minimized.

Show comment
Hide comment
@rafaelfranca

rafaelfranca Jan 21, 2015

Member

👍 for the idea from my side. @sgrif WDYT?

Member

rafaelfranca commented Jan 21, 2015

👍 for the idea from my side. @sgrif WDYT?

@sgrif

This comment has been minimized.

Show comment
Hide comment
@sgrif

sgrif Jan 23, 2015

Member

👍 for the idea from me, as well. Would like to review the code again once it's been rebased onto master and had tests added/moved/etc

Member

sgrif commented Jan 23, 2015

👍 for the idea from me, as well. Would like to review the code again once it's been rebased onto master and had tests added/moved/etc

@bogdan

This comment has been minimized.

Show comment
Hide comment
@bogdan

bogdan Jan 23, 2015

Contributor

Updated the PR:

  • Rebased with master
  • Added tests for ActiveModel
  • Decided to keep a separated documentation for #attributes= in activerecord because it includes many DB interaction points
  • Left ActiveRecord::AttributeAssignment tests untouched that now creates some overlap in tests. Thought it is a good idea
  • Made changelog
Contributor

bogdan commented Jan 23, 2015

Updated the PR:

  • Rebased with master
  • Added tests for ActiveModel
  • Decided to keep a separated documentation for #attributes= in activerecord because it includes many DB interaction points
  • Left ActiveRecord::AttributeAssignment tests untouched that now creates some overlap in tests. Thought it is a good idea
  • Made changelog
@arthurnn

This comment has been minimized.

Show comment
Hide comment
@arthurnn

arthurnn Jan 23, 2015

Member

I would use this on Mongoid if this is merge on ActiveModel.!

Member

arthurnn commented Jan 23, 2015

I would use this on Mongoid if this is merge on ActiveModel.!

+ _assign_attributes(sanitize_for_mass_assignment(attributes))
+ end
+
+ def _assign_attributes(attributes)

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

Should not this method be private?

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

Should not this method be private?

+ @attribute = attribute.to_s
+ super("unknown attribute '#{attribute}' for #{@record.class}.")
+ end
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+
+ # Raised when unknown attributes are supplied via mass assignment.
+ class UnknownAttributeError < NoMethodError
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+require 'active_support/hash_with_indifferent_access'
+
+class AttributeAssignmentTest < ActiveModel::TestCase
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+
+class AttributeAssignmentTest < ActiveModel::TestCase
+
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+
+
+ class Model
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+ end
+
+ class ProtectedParams < ActiveSupport::HashWithIndifferentAccess
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+ test "assign private attribute" do
+ model = Model.new
+ assert_raises ActiveModel::AttributeAssignment::UnknownAttributeError do
+ model.assign_attributes(metadata: {a: 1})

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

space after { and before }

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

space after { and before }

+
+module ActiveModel
+ module AttributeAssignment
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

@@ -208,5 +189,6 @@ def extract_max_param(upper_cap = 100)
[values.keys.max, upper_cap].min
end
end
+

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

✂️

+ end
+ return if new_attributes.blank?
+
+ attributes = new_attributes.stringify_keys

This comment has been minimized.

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

no need these extra whitespaces

@rafaelfranca

rafaelfranca Jan 23, 2015

Member

no need these extra whitespaces

@rafaelfranca rafaelfranca added this to the 5.0.0 milestone Jan 23, 2015

@bogdan

This comment has been minimized.

Show comment
Hide comment
@bogdan

bogdan Jan 23, 2015

Contributor

@rafaelfranca removed all whitespace

Contributor

bogdan commented Jan 23, 2015

@rafaelfranca removed all whitespace

activemodel/CHANGELOG.md
@@ -1,3 +1,23 @@
+* Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributesAssignment`

This comment has been minimized.

@vipulnsward

vipulnsward Jan 23, 2015

Member

AttributesAssignment => AttributeAssignment

@vipulnsward

vipulnsward Jan 23, 2015

Member

AttributesAssignment => AttributeAssignment

activemodel/CHANGELOG.md
+ cat = Cat.new
+ cat.assign_attributes(name: "Gorby", status: "yawning")
+ cat.name # => 'Gorby'
+ cate.status => 'yawning'

This comment has been minimized.

+ # cat = Cat.new
+ # cat.assign_attributes(name: "Gorby", status: "yawning")
+ # cat.name # => 'Gorby'
+ # cate.status => 'yawning'

This comment has been minimized.

@vipulnsward

vipulnsward Jan 23, 2015

Member

same here

@vipulnsward

vipulnsward Jan 23, 2015

Member

same here

+ # cat.name # => 'Gorby'
+ # cat.status => 'sleeping'
+ def assign_attributes(new_attributes)
+ if !new_attributes.respond_to?(:stringify_keys)

This comment has been minimized.

@vipulnsward

vipulnsward Jan 23, 2015

Member

We can check hash type instead of respond here.

@vipulnsward

vipulnsward Jan 23, 2015

Member

We can check hash type instead of respond here.

This comment has been minimized.

@bogdan

bogdan Jan 23, 2015

Contributor

Dunno, extracted it as it is from ActiveRecord. If it should be changed - lets do investigation why it is done like this and change it in another patch.

@bogdan

bogdan Jan 23, 2015

Contributor

Dunno, extracted it as it is from ActiveRecord. If it should be changed - lets do investigation why it is done like this and change it in another patch.

This comment has been minimized.

@sgrif

sgrif Jan 23, 2015

Member

Yeah, I agree with @bogdan here. In general, I'd rather see respond_to? over is_a?.

@sgrif

sgrif Jan 23, 2015

Member

Yeah, I agree with @bogdan here. In general, I'd rather see respond_to? over is_a?.

This comment has been minimized.

@PikachuEXE

PikachuEXE May 19, 2016

Contributor

Why is this using if ! instead of unless?

@PikachuEXE

PikachuEXE May 19, 2016

Contributor

Why is this using if ! instead of unless?

This comment has been minimized.

@theoros

theoros Oct 28, 2016

I personally think it's easier to read if ! than unless...

@theoros

theoros Oct 28, 2016

I personally think it's easier to read if ! than unless...

+
+ def initialize(record, attribute)
+ @record = record
+ @attribute = attribute.to_s

This comment has been minimized.

@vipulnsward

vipulnsward Jan 23, 2015

Member

I think the to_s is extra here.

@vipulnsward

vipulnsward Jan 23, 2015

Member

I think the to_s is extra here.

Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::Attrib…
…utesAssignment`

Allows to use it for any object as an includable module.

@sgrif sgrif merged commit 2606fb3 into rails:master Jan 23, 2015

1 check was pending

continuous-integration/travis-ci The Travis CI build is in progress
Details

sgrif added a commit that referenced this pull request Jan 23, 2015

✂️ and 💅 for #10776
Minor style changes across the board. Changed an alias to an explicit
method declaration, since the alias will not be documented otherwise.

sgrif added a commit that referenced this pull request Jan 23, 2015

Merge pull request #10776 from bogdan/assign-attributes
Extracted attributes assingment from ActiveRecord to ActiveModel
@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Jan 23, 2015

Contributor

❤️

Contributor

egilburg commented Jan 23, 2015

❤️

@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Jan 23, 2015

Contributor

I wonder if, once all non-db-specific logic (including recent one such as the user-facing casting half) is extracted to AM, it would make sense to have a more full-featured out-of-the-box implementation of active model for those that want it to behave not just as a form object but as much as a full AR as possible, except not persisted. That means all the same validations/errors, attribute assignment, casting, etc.

Perhaps call it ActiveModel::Base which would be a richer implementation than ActiveModel::Model, possibly extending it.

Contributor

egilburg commented Jan 23, 2015

I wonder if, once all non-db-specific logic (including recent one such as the user-facing casting half) is extracted to AM, it would make sense to have a more full-featured out-of-the-box implementation of active model for those that want it to behave not just as a form object but as much as a full AR as possible, except not persisted. That means all the same validations/errors, attribute assignment, casting, etc.

Perhaps call it ActiveModel::Base which would be a richer implementation than ActiveModel::Model, possibly extending it.

- end
+ UnknownAttributeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( # :nodoc:
+ 'ActiveRecord::UnknownAttributeError',
+ 'ActiveModel::AttributeAssignment::UnknownAttributeError'

This comment has been minimized.

@egilburg

egilburg Jan 24, 2015

Contributor

Given that end users may be rescuing from this error in their apps, it seems like a rather long and implementation-specific namespace. Perhaps keep it under ActiveModel::UnknownAttributeError?

@egilburg

egilburg Jan 24, 2015

Contributor

Given that end users may be rescuing from this error in their apps, it seems like a rather long and implementation-specific namespace. Perhaps keep it under ActiveModel::UnknownAttributeError?

This comment has been minimized.

@robin850

robin850 Jan 31, 2015

Member

Yep, I agree there ; this is too implementation-specific! 👍

@robin850

robin850 Jan 31, 2015

Member

Yep, I agree there ; this is too implementation-specific! 👍

This comment has been minimized.

@sgrif

sgrif Jan 31, 2015

Member

Agreed, let's leave this undeprecated, and just alias it. It's an unnecessary change for users, and forces them to care too much about implementation.

@sgrif

sgrif Jan 31, 2015

Member

Agreed, let's leave this undeprecated, and just alias it. It's an unnecessary change for users, and forces them to care too much about implementation.

This comment has been minimized.

@sgrif

sgrif Jan 31, 2015

Member

@robin850 Would you care to open a PR?

@sgrif

sgrif Jan 31, 2015

Member

@robin850 Would you care to open a PR?

This comment has been minimized.

@bogdan

bogdan Jan 31, 2015

Contributor

Leaving this undeprecated can cause confusion because while constant alias will work the caught exception instances will still have new name. It looks like people are just yelling about too long name space and want it to be sorter like in @egilburg's comment

@bogdan

bogdan Jan 31, 2015

Contributor

Leaving this undeprecated can cause confusion because while constant alias will work the caught exception instances will still have new name. It looks like people are just yelling about too long name space and want it to be sorter like in @egilburg's comment

@robin850 robin850 referenced this pull request Feb 25, 2015

Merged

Follow-up to #10776 #19077

robin850 added a commit to robin850/rails that referenced this pull request Feb 26, 2015

Follow-up to #10776
The name `ActiveModel::AttributeAssignment::UnknownAttributeError` is
too implementation specific so let's move the constant directly under
the ActiveModel namespace.

Also since this constant used to be under the ActiveRecord namespace, to
make the upgrade path easier, let's avoid raising the former constant
when we deal with this error on the Active Record side.

@rafaelfranca rafaelfranca modified the milestones: 5.0.0 [temp], 5.0.0 Dec 30, 2015

@m0ur3n

This comment has been minimized.

Show comment
Hide comment
@m0ur3n

m0ur3n Jul 20, 2016

should it be

cat.status # => 'sleeping'

should it be

cat.status # => 'sleeping'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment