-
Notifications
You must be signed in to change notification settings - Fork 899
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
Add validations for MiqWidgetSet #20890
Add validations for MiqWidgetSet #20890
Conversation
377a955
to
356a1ec
Compare
app/models/miq_widget_set.rb
Outdated
before_save :keep_group_when_saving | ||
after_save :update_members | ||
|
||
validates :group_id, :presence => true, :unless => ->(x) { x.read_only? } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think you can do
validates :group_id, :presence => true, :unless => ->(x) { x.read_only? } | |
validates :group_id, :presence => true, :unless => :read_only? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call jason.
Also, thanks Libor for validating group_id and not group - much better for performance in production but can act strange in development with factories
app/models/miq_widget_set.rb
Outdated
|
||
if description_changed? && MiqWidgetSet.exists?(search_params) | ||
errors.add(:error, "Description (Tab Title) must be unique for this group") | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like a regular uniqueness clause should work when added with a scope on owner_id...
Something like...
validates :description, :uniqueness => {:scope => :owner_id}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call.
I do notice that this only really does something on description changed
so maybe
end | |
validates :description, :uniqueness_when_changed => {:scope => :owner_id} |
@kbrock Can you please review? I'm not sure on the rest, and I think you've been working in this space recently. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks nice - only a few nits.
my only complaints are code that Libor did not introduce (using sets, serializing the settings data instead of using habtm/through)
def update_members | ||
replace_children(Array(set_data_widgets)) | ||
current_user = User.current_user | ||
members.each { |w| w.create_initial_content_for_user(current_user.userid) } if current_user # Generate content if not there |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For an update where the widget is already all set for the user, will create_initial_content_for_user()
modify the member and cause an unnecessary save? or is it smart enough to not change it.
I never trust serialized values changed? values in rails 5.x (or maybe this wasn't fixed until 6.1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For an update where the widget is already all set for the user, will
create_initial_content_for_user()
modify the member and cause an unnecessary save? or is it smart enough to not change it.I never trust serialized values changed? values in rails 5.x (or maybe this wasn't fixed until 6.1)
create_initial_content_for_user
contains report generation and it doesn't matter if we are re-generating it with same members - content of report generation could be different independently on members.
and replace_children(Array(set_data_widgets))
is not needed execute when
members.map(&:id) == set_data_widgets.ids
so I fixed it.
I never trust serialized values changed? values in rails 5.x (or maybe this wasn't fixed until 6.1)
I am not sure how do you mean this, I don't see usage serialize attributes here.
thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the method names I had assumed it did nothing if called multiple times. But instead it looks like it will change the data every time.
Do we really want to update the set data automatically on every save?
app/models/miq_widget_set.rb
Outdated
before_save :keep_group_when_saving | ||
after_save :update_members | ||
|
||
validates :group_id, :presence => true, :unless => ->(x) { x.read_only? } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call jason.
Also, thanks Libor for validating group_id and not group - much better for performance in production but can act strange in development with factories
app/models/miq_widget_set.rb
Outdated
|
||
if description_changed? && MiqWidgetSet.exists?(search_params) | ||
errors.add(:error, "Description (Tab Title) must be unique for this group") | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call.
I do notice that this only really does something on description changed
so maybe
end | |
validates :description, :uniqueness_when_changed => {:scope => :owner_id} |
app/models/miq_widget_set.rb
Outdated
def add_to_dashboard_order | ||
return unless group_id | ||
|
||
group = MiqGroup.find(group_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this use an association?
belongs_to :group, :class_name => 'MiqGroup'
app/models/miq_widget_set.rb
Outdated
errors.add(:base, _("Unable to delete read-only WidgetSet")) if read_only? | ||
throw :abort unless errors[:base].empty? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this trying to catch errors not added by this class?
or should this all go into a block?
errors.add(:base, _("Unable to delete read-only WidgetSet")) if read_only? | |
throw :abort unless errors[:base].empty? | |
if read_only? | |
errors.add(:base, _("Unable to delete read-only WidgetSet")) | |
throw :abort | |
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks that throw :abort
is not needed here (I don't see errors.add(:base)
on other places)
9141a6d
to
f932252
Compare
986f475
to
0dd0d5c
Compare
I also added spec for validations and ManageIQ/manageiq-ui-classic#7561 is needed to merge first otherwise it will break UI. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a little nervous about the implications of always having a group set.
I guess the waters get muddy because most users have a single current_group so not setting it probably worked well in the past.
If we need to do this, I like having group_id auto set in a before_validator hook rather than expect the caller to set this. (is it always set? or only for particular owners?)
@@ -14,6 +29,31 @@ def self.with_users | |||
where.not(:userid => nil) | |||
end | |||
|
|||
def update_members | |||
replace_children(Array(set_data_widgets)) if members.map(&:id) != set_data_widgets.ids |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A. Do we want to make this order independent, or does order matter?
replace_children(Array(set_data_widgets)) if members.map(&:id) != set_data_widgets.ids | |
replace_children(Array(set_data_widgets)) if members.map(&:id)&.sort != set_data_widgets.ids.sort |
def update_members | ||
replace_children(Array(set_data_widgets)) | ||
current_user = User.current_user | ||
members.each { |w| w.create_initial_content_for_user(current_user.userid) } if current_user # Generate content if not there |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the method names I had assumed it did nothing if called multiple times. But instead it looks like it will change the data every time.
Do we really want to update the set data automatically on every save?
def add_to_dashboard_order(widget_set_id) | ||
self.settings ||= {:dashboard_order => []} | ||
self.settings[:dashboard_order] ||= [] | ||
self.settings[:dashboard_order] |= [widget_set_id] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assuming this is the logic that was in the ui.
also thinking this is |=
instead of +=
to fix any duplicate issues
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes it is logic from UI,
suggested change is not needed:
irb(main):004:0> a=[1];a+=[1];b=[2];b|=[2];puts "a:#{a.count} b:#{b.count}";
a:2 b:1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def delete_from_dashboard_order | ||
return unless group_id | ||
|
||
group = MiqGroup.find(group_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason why group is looked up here but not in add_to_dashboard_order
also, this looks strange instead of having a belongs_to
(it may be due to wanting to blow out the cache?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed 👍
validate :set_data do | ||
errors.add(:set_data, "One widget must be selected(set_data)") if widget_ids.empty? | ||
|
||
filtered_widget_ids = set_data_widgets.pluck(:id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason not to use widget_ids
here?
is it trying to verify the values in the database?
it looks like those ids are stored in the set_data hash.
maybe you are also validating that these records exist in the database?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes as I you said because we need to validate whether those ids exist.
end | ||
|
||
before(:create) do |x| | ||
x.group_id = x.owner_id if x.owner_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not comfortable with this.
Can we do this in a before_validation callback rather than have part of the object creation contract? Which to be honest, the purpose of this PR is to remove the object creation process from the ui-classic and put into the model.
For some reason, this feels like it glosses over an implementation detail and I'm more comfortable having this requirement exposed in the spec or as mentioned before, completely removed.
@Fryguy may feel otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes removing thanks to new before_validation
@@ -2,5 +2,17 @@ | |||
factory :miq_widget_set do | |||
sequence(:name) { |n| "widget_set_#{seq_padded_for_sorting(n)}" } | |||
sequence(:description) { |n| "widget_set_#{seq_padded_for_sorting(n)}" } | |||
|
|||
trait :set_data_with_one_widget do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what this buys us.
the old code feels the same.
I also like having us add the widgets using the same methods we use to add them in the production app (not specs)
If something changes in the way that add works or a bug is introduced then it could slip through.
Yes, I know this could be said about a lot of factories, but this is directly manipulating a hash with funky workings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes removing thanks to new before_validation, thanks
end | ||
|
||
def ensure_can_be_destroyed | ||
errors.add(:base, _("Unable to delete read-only WidgetSet")) if read_only? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from what I'm reading, I think the raise :abort
is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it 👍
@@ -88,6 +128,7 @@ def self.copy_dashboard(source_widget_set, destination_name, destination_descrip | |||
:owner_type => "MiqGroup", | |||
:set_type => source_widget_set.set_type, | |||
:set_data => source_widget_set.set_data, | |||
:group_id => assign_to_group.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think this should come across automatically from owner_id (in the before_validation)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's right - it is removed 👍
end | ||
|
||
def widget_ids | ||
SET_DATA_COLS.map { |x| set_data[x] }.flatten.compact |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is new code, could you change to use flat_map
If this is existing code that you moved, then it is up to you
SET_DATA_COLS.map { |x| set_data[x] }.flatten.compact | |
SET_DATA_COLS.flat_map { |x| set_data[x] }.compact |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it
0dd0d5c
to
b203865
Compare
Checked commit lpichler@b203865 with ruby 2.6.3, rubocop 0.82.0, haml-lint 0.35.0, and yamllint |
we have it since #19491 and it is because having same name in different category. |
@kbrock thanks for you review and I added changes as you suggested 👍 |
In order to add MiqWidgetSet API Endpoint following validations and update on related objects have been extracted from UI dashboard controller:
Name cannot be changed during UPDATE(only API)set_data
PR also adds initializaation of set_data and
methods
update_membersand
delete_from_dashboard_order,
add_to_dashboard_order `@miq-bot add_label refactoring
Links