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

Only perform regexes on Strings #3616

Merged

Conversation

jacquesporveau
Copy link
Contributor

@jacquesporveau jacquesporveau commented May 8, 2020

Description
We were getting warnings from Ruby 2.7

warning: deprecated Object#=~ is called on TrueClass; it always returns
nil

In the case that true was passed in as a preference here we would try to
perform a regex on it and get nil from value =~ //.

This meant that the conditional would not be met and this case would not
return false but instead return true.

This is the correct behaviour but it's pretty sketchy. We should not be
relying on what calling regex matching methods on things that aren't
strings return here.

Ref #3611

Checklist:

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have updated Guides and README accordingly to this change (if needed)
  • I have added tests to cover this change (if needed)
  • I have attached screenshots to this PR for visual changes (if needed)

Copy link
Member

@jarednorman jarednorman left a comment

Choose a reason for hiding this comment

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

I really don't like to see class checks, but I'm not sure I have an immediately better suggestion... unless maybe we added a short-circuit for the case when value is already boolean to the start of this conditional?

if [true, false].include?(value)
  value
elsif # ...the exist conditional

@aldesantis
Copy link
Member

I like @jarednorman's idea of a shortcircuit, I think it's slightly better than checking the class. I haven't seen any examples in the wild, but I imagine there are cases where users have passed something that is not a String but still implements #=~ — we should still play nice in that case.

The only problem I see with the shortcircuit approach is that we will still call =~ on all other objects that are neither true nor false, so if the user passes an object that is neither a string nor a boolean, we'll still get that warning. Since that's not a supported use case and would be the user's mistake though, I'm not terribly worried about it.

@jacquesporveau
Copy link
Contributor Author

Sounds like there is a learning opportunity here for me. I'd like to try and understand why we don't like to check classes. Could you give me a run down? Maybe that will help me get on the same page as far as looking for another solution.

@jarednorman
Copy link
Member

jarednorman commented May 11, 2020

There's often a desire in Ruby to embrace duck-typing. This usually means preferring to check an object responds to the method in question before calling it and not worrying about whether the object is of a particular class. Our code should just care that the responds to the methods we want to call ("quacks like a duck").

(Edit: grammar)

@softr8
Copy link
Contributor

softr8 commented May 12, 2020

It looks like bad idea, but what about:

when :boolean
  ( !value || value.to_s =~ /\A(f|false|0|^)\Z/i ) ? false : true

it checks for '0', 'f', 'false' and ''

@jarednorman
Copy link
Member

It does look bad, but I'm having trouble thinking of a reason that we shouldn't do it.

@jacquesporveau
Copy link
Contributor Author

Sry I've been inactive. I'll give that a go and see if we need to add anymore tests shortly.

@jacquesporveau
Copy link
Contributor Author

I think this is what we are looking for. Couldn't just paste in Edwin's solution because apparently we also supported empty arrays here too so needed to cover that as well.

@@ -158,8 +158,7 @@ def convert_preference_value(value, type)
value.to_i
when :boolean
if !value ||
value == 0 ||
value =~ /\A(f|false|0)\Z/i ||
value.to_s =~ /\A(f|false|0|^)\Z/i ||
Copy link
Member

@aldesantis aldesantis May 22, 2020

Choose a reason for hiding this comment

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

Does it make sense to use this helper instead?

ActiveModel::Type::Boolean.new.cast(value)

Looking at the documentation, it seems to do what we need.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I prefer that. It's arguably a breaking change though and one that might be weird to use a config option to control. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, yeah, we may decide to either hold off on this until 3.1 or merge the current version now and migrate to ActiveModel's API in 3.1.

@solidusio/core-team any other thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I'm fine with merging as is, though I'm curious why you'd want to make the switch the the ActiveModel API in 3.1 rather than 3.0. Wouldn't that be a breaking change that would be better off rolling into 3.0?

Copy link
Member

@aldesantis aldesantis May 26, 2020

Choose a reason for hiding this comment

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

Sorry, that comment didn't make any sense. 😅 I meant 3.0, not 3.1. The problem is that ideally we don't want any changes at all in 3.0: we should just remove all deprecations introduced in previous versions of Solidus. The idea is that, if you're on 2.11 and not seeing any deprecations, you can safely upgrade to 3.0.

I think one approach could be to print a deprecation warning if the value of a configuration option is one of the values that is now being treated as true, but would be treated as false by ActiveModel. Something like the following:

if value.in?(ActiveModel::Type::Boolean::FALSE_VALUES - ['f', 'false', '0'])
  Spree::Deprecation.warn "#{value.inspect} will be treated as false starting in Solidus 3.0."
end

# ...

The check would have to be a bit more complex, since it looks like we currently treat any object that responds to #empty? and returns true as false, while ActiveRecord only seems to do it for empty strings, and even then, they return nil rather than false.

We would do this in 2.11, and then upgrade to the new behavior in 3.0.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I agree; that would be ideal. I think we're getting away from the original intent of this PR, though. The goal here was to address a deprecation where true is being passed into this class which isn't actually handled by the above deprecation suggestion.

It's more work then I'm sure @jacquesporveau had intended to do here, but maybe we want to leave the existing logic, but wrap it in a conditional deprecation warning that's fired if the existing logic doesn't produce the same value as the ActiveModel version (potentially with a config option to use the new ActiveModel logic instead.) In v3.0 we remove the old logic and move forward with only the ActiveModel version.

@jacquesporveau Apologies for all the back and forth on this one. We're trying to be careful in deciding what belongs in v2.11 since it's our last chance to deprecate things ahead of Solidus v3.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sa'll good

Copy link
Member

@gmacdougall gmacdougall left a comment

Choose a reason for hiding this comment

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

I'm good with this as is.

Copy link
Member

@aldesantis aldesantis left a comment

Choose a reason for hiding this comment

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

Discussed in the core team meeting, we decided to hold off on the ActiveModel API for the time being.

@aldesantis
Copy link
Member

@jacquesporveau looks like CI is not running here, could you take a look? 🙏

Recently we started getting warnings about calling #=~ on things like
booleans and arrays etc.

This commit ensures that we cast the argument to a string before
performing a regex on it to ensure that we aren't relying on what
calling a regex match type method on a boolean returns.
@jacquesporveau
Copy link
Contributor Author

Looks like I had to authorize circle to have my code ran by CI. Weird.

Anyways it looks like everything but ci/circleci: postgres_rails_master_activestorage is passing which seems to be a common theme looking at the last couple of PRs. Seems like the CI workflow might be having a version problem or something like that.

I am not going to look into why that CI check is failing.

@aldesantis
Copy link
Member

No worries, it's not related to the changes. Thanks @jacquesporveau!

@aldesantis aldesantis merged commit 8263d90 into solidusio:master Jun 1, 2020
@jacquesporveau jacquesporveau deleted the jacquesporveau/ruby-warnings branch June 1, 2020 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants