Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update attribute_builder for rails change #17996

Merged
merged 1 commit into from
Sep 25, 2018

Conversation

kbrock
Copy link
Member

@kbrock kbrock commented Sep 19, 2018

rails 5.0.7 [changed] the way attribute_builder works.
This updates attribute_builder to work accordingly

The [old code] used a block. The [new code] uses an array - We are now modifying the array instead of passing the block.

Also, 5.0.0 changed the way dirty worked.
Think the change to forgetting_assignment introduced this problem for
all rails users, but it is subtle and would only be noticed by
people who use lots of non-database attributes (e.g.: us)
The bug remains in 5.1.0 but the code changes significantly in 5.2

This fixes an issue seen by #17912

Thnx @gmcculloug and @d-m-u for tracking down the bug and the pairing. Looks like it was introduced in rails 5.0.7
/cc @NickLaMuro @jrafanie you guys saw this original patch.

The Attribute change should go into rails proper, but they won't patch anything before 5.2 (we're on 5.0.7 right now), and the code has changed so much that I'm not sure it is even necessary.

@kbrock
Copy link
Member Author

kbrock commented Sep 19, 2018

/cc @gtanzillo This does work with virtual_attributes - and I know you and I have merged some code with that. so just keeping you in the loop

Copy link
Contributor

@d-m-u d-m-u left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@d-m-u
Copy link
Contributor

d-m-u commented Sep 19, 2018

Tested with the changes in #17912, works like a beaut.

_default_attributes[name].dup
end
def attributes_builder # :nodoc:
unless defined?(@attributes_builder) && @attributes_builder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this saying? defined and not nil? Why change this from @ivar ||= ? It seems unrelated.

Plus, I mentally convert unless to if !xxx so && requires me to think whereas the original feels easier to understand.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you concerned with setting this to false and not memoizing false properly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely because we tend to straight copy upstream and patch a part of it (so this code may actually be upstream code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -400,12 +400,15 @@ def virtual_attribute_names
end
end

def attributes_builder
@attributes_builder ||= ::ActiveRecord::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
unless columns_hash.key?(name) || virtual_attribute?(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless || 🏃 🙀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrafanie this was by me, and yes, it was intentional. This is patching over the original Rails method that only had unless columns_hash.key(name) at the time, and for easier comparison with our || virtual_attribute?(name) addition, I left it as the less pretty unless || you speak of.

In less words: #dealWithIt™

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha, yeah, now I remember. I only say it because I literally have to make mental truth tables with unless || so they might save real estate but I can't grok them easily.

def attributes_builder
@attributes_builder ||= ::ActiveRecord::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
unless columns_hash.key?(name) || virtual_attribute?(name)
_default_attributes[name].dup
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this block was doing before. Before, we created a Builder object with the attribute_types and primary_key, did some dup'ing of attributes in the block. Now, we're filtering out the primary_key and the virtual attributes and creating the Builder with all attributes minus these two categories of attributes.

So, why are we filtering primary_key when we didn't before? Why are we now passing other non-virtual attributes to the Builder when we didn't before?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was here previously was exactly what was in the Rails codebase, with the addition of || virtual_attribute?(name) (for our purposes).

I think what @kbrock did here was just do the same for the new changes that are now in 5.0.7, which has a bug fix that we need. Maybe some links in the description would help clear some of the confusion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I can't follow what was happening before and why we changed how we instantiated the Builder objects without looking at the rails internals.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link that @d-m-u was passing around yesterday might help provide some clarity/context:

https://gitter.im/ManageIQ/manageiq/core/archives/2017/01/05

Though I personally prefer the ?at=[TIMESTAMP] link since there is more discussion the next day that the previous link doesn't show:

https://gitter.im/ManageIQ/manageiq/core?at=586e8f5b61e516c157900687

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to just merge this but I have no idea why these changes are needed. I understand the issue but am totally lost as to why the builder instantiation was changed. 🤷‍♂️ I don't get why primary key was removed. Did we pull down a new version of the method from rails 5.0.7? Did we patch it on top of that? It's unclear.

Copy link
Member Author

@kbrock kbrock Sep 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrafanie the old code is based upon rails 5.0.0 - 5.0.6 [ref]
But the method changed [here].

I updated it to match rails 5.0.7 implementation of this method. [ref]

class Attribute
def with_value_from_database(value)
# self.class.from_database(name, value, type)
initialized? ? self.class.from_database(name, value, type) : self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, why? Please provide a two line comment explaining why we need this. I'm not sure of the why here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kbrock your comment about says this is no longer needed for 5.2, can you explain why this is needed for 5.0 and 5.1? You mention a bug... can you provide summary and link here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we need to remove this with 5.2? Do we need a warning or something if this code is run against 5.2?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all changed in 5.2 - so yes, we will need to remove it.

I didn't know the proper way to warn. snippet reference?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to clean this up later, we can warn. If we just want to patch it for 5.0 and 5.1, you can just put a guard clause `return if Rails::VERSION >= "5.2.0" or something like that...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to just do this in a followup, I'm fine with that

@jrafanie
Copy link
Member

Is this something we have tests around? Can we add them if not? It feels like @d-m-u's issue should be easy to wrap a test around.

@Fryguy
Copy link
Member

Fryguy commented Sep 20, 2018

a minimal bit of code that shows the issue:

irb(main):001:0> s = Service.last
=> #<Service id: 364, name: "Test", description: nil, guid: "cc25b8a7-d495-402c-bfae-ed6bdb911e7b", type: nil, service_template_id: nil, options: {}, display: false, created_at: "2018-09-17 20:07:45", updated_at: "2018-09-18 12:40:32", evm_owner_id: nil, miq_group_id: 1, retired: false, retires_on: nil, retirement_warn: nil, retirement_last_warn: nil, retirement_state: nil, retirement_requester: nil, tenant_id: 1, ancestry: nil, initiator: "user">
irb(main):002:0> s.attributes
=> {"id"=>364, "name"=>"Test", "description"=>nil, "guid"=>"cc25b8a7-d495-402c-bfae-ed6bdb911e7b", "type"=>nil, "service_template_id"=>nil, "options"=>{}, "display"=>false, "created_at"=>Mon, 17 Sep 2018 20:07:45 UTC +00:00, "updated_at"=>Tue, 18 Sep 2018 12:40:32 UTC +00:00, "evm_owner_id"=>nil, "miq_group_id"=>1, "retired"=>false, "retires_on"=>nil, "retirement_warn"=>nil, "retirement_last_warn"=>nil, "retirement_state"=>nil, "retirement_requester"=>nil, "tenant_id"=>1, "ancestry"=>nil, "initiator"=>"user"}
irb(main):003:0> s.update_attributes(:display => true)
=> true
irb(main):004:0> s.attributes
=> {"id"=>364, "name"=>"Test", "description"=>nil, "guid"=>"cc25b8a7-d495-402c-bfae-ed6bdb911e7b", "type"=>nil, "service_template_id"=>nil, "options"=>{}, "display"=>true, "created_at"=>Mon, 17 Sep 2018 20:07:45 UTC +00:00, "updated_at"=>Tue, 18 Sep 2018 12:55:52 UTC +00:00, "evm_owner_id"=>nil, "miq_group_id"=>1, "retired"=>false, "retires_on"=>nil, "retirement_warn"=>nil, "retirement_last_warn"=>nil, "retirement_state"=>nil, "retirement_requester"=>nil, "tenant_id"=>1, "ancestry"=>nil, "initiator"=>"user", "href_slug"=>nil, "region_number"=>nil, "region_description"=>nil, "owned_by_current_user"=>nil, "owned_by_current_ldap_group"=>nil, "custom_1"=>nil, "custom_2"=>nil, "custom_3"=>nil, "custom_4"=>nil, "custom_5"=>nil, "custom_6"=>nil, "custom_7"=>nil, "custom_8"=>nil, "custom_9"=>nil, "cpu_usagemhz_rate_average_avg_over_time_period"=>nil, "cpu_usagemhz_rate_average_low_over_time_period"=>nil, "cpu_usagemhz_rate_average_high_over_time_period"=>nil, "cpu_usage_rate_average_avg_over_time_period"=>nil, "cpu_usage_rate_average_low_over_time_period"=>nil, "cpu_usage_rate_average_high_over_time_period"=>nil, "mem_usage_absolute_average_avg_over_time_period"=>nil, "mem_usage_absolute_average_low_over_time_period"=>nil, "mem_usage_absolute_average_high_over_time_period"=>nil, "derived_memory_used_avg_over_time_period"=>nil, "derived_memory_used_low_over_time_period"=>nil, "derived_memory_used_high_over_time_period"=>nil, "max_cpu_usage_rate_average_avg_over_time_period"=>nil, "max_cpu_usage_rate_average_low_over_time_period"=>nil, "max_cpu_usage_rate_average_high_over_time_period"=>nil, "max_mem_usage_absolute_average_avg_over_time_period"=>nil, "max_mem_usage_absolute_average_low_over_time_period"=>nil, "max_mem_usage_absolute_average_high_over_time_period"=>nil, "max_cpu_usage_rate_average_avg_over_time_period_without_overhead"=>nil, "max_cpu_usage_rate_average_low_over_time_period_without_overhead"=>nil, "max_cpu_usage_rate_average_high_over_time_period_without_overhead"=>nil, "max_mem_usage_absolute_average_avg_over_time_period_without_overhead"=>nil, "max_mem_usage_absolute_average_low_over_time_period_without_overhead"=>nil, "max_mem_usage_absolute_average_high_over_time_period_without_overhead"=>nil, "aggregate_direct_vm_cpus"=>nil, "aggregate_direct_vm_memory"=>nil, "aggregate_direct_vm_disk_count"=>nil, "aggregate_direct_vm_disk_space_allocated"=>nil, "aggregate_direct_vm_disk_space_used"=>nil, "aggregate_direct_vm_memory_on_disk"=>nil, "aggregate_all_vm_cpus"=>nil, "aggregate_all_vm_memory"=>nil, "aggregate_all_vm_disk_count"=>nil, "aggregate_all_vm_disk_space_allocated"=>nil, "aggregate_all_vm_disk_space_used"=>nil, "aggregate_all_vm_memory_on_disk"=>nil, "v_total_vms"=>nil, "has_parent"=>nil, "power_state"=>nil, "power_status"=>nil, "service_id"=>nil, "evm_owner_email"=>nil, "evm_owner_name"=>nil, "evm_owner_userid"=>nil, "owning_ldap_group"=>nil}

@Fryguy
Copy link
Member

Fryguy commented Sep 20, 2018

Better one:

[1] pry(main)> z = Zone.first
[2] pry(main)> z.attributes
=> {"id"=>21000000000001,
 "name"=>"default",
 "description"=>"Default Zone",
 "created_on"=>Fri, 04 May 2018 22:05:45 UTC +00:00,
 "updated_on"=>Fri, 04 May 2018 22:05:45 UTC +00:00,
 "settings"=>{},
 "log_file_depot_id"=>nil}
[3] pry(main)> z.save!
[4] pry(main)> z.attributes
=> {"id"=>21000000000001,
 "name"=>"default",
 "description"=>"Default Zone",
 "created_on"=>Fri, 04 May 2018 22:05:45 UTC +00:00,
 "updated_on"=>Fri, 04 May 2018 22:05:45 UTC +00:00,
 "settings"=>{},
 "log_file_depot_id"=>nil,
 "href_slug"=>nil,
 "region_number"=>nil,
 "region_description"=>nil,
 "authentication_status"=>nil,
 "cpu_usagemhz_rate_average_avg_over_time_period"=>nil,
 "cpu_usagemhz_rate_average_low_over_time_period"=>nil,
 "cpu_usagemhz_rate_average_high_over_time_period"=>nil,
 "cpu_usage_rate_average_avg_over_time_period"=>nil,
 "cpu_usage_rate_average_low_over_time_period"=>nil,
 "cpu_usage_rate_average_high_over_time_period"=>nil,
 "mem_usage_absolute_average_avg_over_time_period"=>nil,
 "mem_usage_absolute_average_low_over_time_period"=>nil,
 "mem_usage_absolute_average_high_over_time_period"=>nil,
 "derived_memory_used_avg_over_time_period"=>nil,
 "derived_memory_used_low_over_time_period"=>nil,
 "derived_memory_used_high_over_time_period"=>nil,
 "max_cpu_usage_rate_average_avg_over_time_period"=>nil,
 "max_cpu_usage_rate_average_low_over_time_period"=>nil,
 "max_cpu_usage_rate_average_high_over_time_period"=>nil,
 "max_mem_usage_absolute_average_avg_over_time_period"=>nil,
 "max_mem_usage_absolute_average_low_over_time_period"=>nil,
 "max_mem_usage_absolute_average_high_over_time_period"=>nil,
 "max_cpu_usage_rate_average_avg_over_time_period_without_overhead"=>nil,
 "max_cpu_usage_rate_average_low_over_time_period_without_overhead"=>nil,
 "max_cpu_usage_rate_average_high_over_time_period_without_overhead"=>nil,
 "max_mem_usage_absolute_average_avg_over_time_period_without_overhead"=>nil,
 "max_mem_usage_absolute_average_low_over_time_period_without_overhead"=>nil,
 "max_mem_usage_absolute_average_high_over_time_period_without_overhead"=>nil,
 "aggregate_cpu_speed"=>nil,
 "aggregate_cpu_total_cores"=>nil,
 "aggregate_physical_cpus"=>nil,
 "aggregate_memory"=>nil,
 "aggregate_vm_cpus"=>nil,
 "aggregate_vm_memory"=>nil,
 "aggregate_disk_capacity"=>nil}

rails 5.0.7 changed the way attribute_builder works
This updates attribute_builder to work accordingly

Also, 5.0.0 changed the way dirty worked.
Think the change to forgetting_assignment introduced this problem for
all rails versions, but it is subtle and would only be noticed by
people who use lots of non-database attributes (e.g.: us)
It remains in 5.1.0 but the code changes significantly in 5.2
@kbrock kbrock force-pushed the virtual_attributes_after_save branch from 37f1365 to a687c22 Compare September 25, 2018 16:17
@miq-bot
Copy link
Member

miq-bot commented Sep 25, 2018

Checked commit kbrock@a687c22 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0
2 files checked, 0 offenses detected
Everything looks fine. 🏆

@kbrock
Copy link
Member Author

kbrock commented Sep 25, 2018

Thanks for review all.

Added a test

Added references to the code before / after. I'll move them into the description.

Copy link
Member

@jrafanie jrafanie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Let's do the rails version check in a followup PR. We "can't touch this" branch right now, so will wait for @Fryguy to hammer out the new release branch and give the go ahead and then we'll merge.

@jrafanie jrafanie merged commit 677bf46 into ManageIQ:master Sep 25, 2018
@jrafanie jrafanie added this to the Sprint 96 Ending Oct 8, 2018 milestone Sep 25, 2018
@jrafanie jrafanie self-assigned this Sep 25, 2018
@jrafanie
Copy link
Member

@kbrock @d-m-u I'll let you decide what branches this needs to go back to... and create the bugzillas for it. 🙇

@kbrock kbrock deleted the virtual_attributes_after_save branch September 25, 2018 19:40
@kbrock
Copy link
Member Author

kbrock commented Sep 25, 2018

I just work here - I defer to @d-m-u to decide which version to use

@d-m-u
Copy link
Contributor

d-m-u commented Sep 25, 2018

@miq-bot add_label hammer/yes
¯_(ツ)_/¯

@gmcculloug
Copy link
Member

I agree with @d-m-u that the hammer branch would be preferred. We are covered by the work-around in PR #17912 here app/models/service_retire_task.rb:41.

However, If this change gets back-ported we could also remove the work-around on hammer with PR #18019.

simaishi pushed a commit that referenced this pull request Sep 26, 2018
update attribute_builder for rails change

(cherry picked from commit 677bf46)
@simaishi
Copy link
Contributor

Hammer backport details:

$ git log -1
commit c6d4787b9a957ab752102e7ae5ba7b9c6a99b6f1
Author: Joe Rafaniello <[email protected]>
Date:   Tue Sep 25 15:09:24 2018 -0400

    Merge pull request #17996 from kbrock/virtual_attributes_after_save
    
    update attribute_builder for rails change
    
    (cherry picked from commit 677bf4601627a588c95c33efe47f76ea6998f0ef)

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

Successfully merging this pull request may close these issues.

8 participants