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

Adds possibility to define allowed values for tags #40

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

jplethier
Copy link

@jplethier jplethier commented Jun 16, 2022

Proposal

This PR proposes to have a easy way to add simple validations for tags allowed to model using this gem

Changes

  • Adds an allowed option as parameters for acts_as_taggable_array_on to define allowed values for tags
  • Adds a validation method when the allowed param is present and is valid to ensure that all tags values are allowed
  • Adds a check to ensure that the allowed param is an array when it is present, raising an error if it is not an array

@skatkov
Copy link
Collaborator

skatkov commented Jun 22, 2022

Thanks for your contribution.

Can you please rebase your changes and update README accordingly?

@jplethier
Copy link
Author

@skatkov I've just updated the branch with all new changes and updated readme adding information about allow list option. Please review the readme and give me any feedback if it can be more clear on better in any other way

context "when options is not an array" do
it "raises an error" do
expect { User.acts_as_taggable_array_on :colors, allow_list: "red, blue" }
.to raise_error(described_class::InvalidAllowListTypeError)
Copy link
Collaborator

@skatkov skatkov Jun 26, 2022

Choose a reason for hiding this comment

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

I've noticed, that your adding an .error to AR object. Maybe it's good to test that? and make sure that .valid? returns "false"?

Copy link
Author

@jplethier jplethier Jun 28, 2022

Choose a reason for hiding this comment

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

I don't know if I understand correct the suggestiont, but this specific tests is about tag definition, not about the active record validation. I just added error to be raised when the tags definition is invalid, in this case, the allow list is invalid. Does that make sense?

Copy link
Collaborator

@skatkov skatkov left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution—I've added some comments and questions. So far, I don't feel comfortable merging this MR as is.

But definitely see this as a valuable addition to a gem and would like to work out details to get this merged.

README.md Outdated Show resolved Hide resolved

context "when options is an array" do
before do
User.acts_as_taggable_array_on :colors, allow_list: %w[red blue]
Copy link
Collaborator

@skatkov skatkov Jun 26, 2022

Choose a reason for hiding this comment

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

Would be interesting to know, what would happen in case of any other datatype passed to allow_list. How can we handle that better?

e.g. string or number:
taggable_array :colors, allow_list: 'red'

or hash:
taggable_array :colors, allow_list: {red: 'red'}

Copy link
Collaborator

@skatkov skatkov Jun 26, 2022

Choose a reason for hiding this comment

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

Also would be interesting to know, how would we comparse arrays with strings inside and symbols. I would expect that ['red'] would equal [:red]

Can we add a test to make sure, that this is a case?

Copy link
Author

Choose a reason for hiding this comment

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

Would be interesting to know, what would happen in case of any other datatype passed to allow_list. How can we handle that better?

e.g. string or number: taggable_array :colors, allow_list: 'red'

or hash: taggable_array :colors, allow_list: {red: 'red'}

About this, there is a check to guarantee that allow list is an array, and if it is not an array, it throws an error saying that allow list must be an array. There is a test for this too https://github.com/tmiyamon/acts-as-taggable-array-on/pull/40/files#diff-ad2deaee3a6d67e0e4d46e72478cb4ea6fc8209a10fa2c244656f3683f2803bcR166

Copy link
Author

Choose a reason for hiding this comment

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

Also would be interesting to know, how would we comparse arrays with strings inside and symbols. I would expect that ['red'] would equal [:red]

Can we add a test to make sure, that this is a case?

I added tests scenarios to this and make sure to deal with those two options as the same

@@ -18,6 +28,16 @@ def acts_as_taggable_array_on(tag_name, *)
scope :"without_any_#{tag_name}", ->(tags) { where.not("#{table_name}.#{tag_name} && ARRAY[?]::#{tag_array_type_fetcher.call}[]", parser.parse(tags)) }
scope :"without_all_#{tag_name}", ->(tags) { where.not("#{table_name}.#{tag_name} @> ARRAY[?]::#{tag_array_type_fetcher.call}[]", parser.parse(tags)) }

if args[:allow_list].present?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we extract :allow_list into a constant?

It seems reasonable to do, since we repeat this at least 3 times in method definition. In case we need to switch this value, we'll have lower chance of typo mistake.

Copy link
Author

Choose a reason for hiding this comment

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

If I understand correctly what you suggested, I did in this commit 8c1aa9d

expect(User.without_any_colors("red, blue")).to match_array([@admin1])
end
end
context "without allow_list" do
Copy link
Collaborator

@skatkov skatkov Jun 26, 2022

Choose a reason for hiding this comment

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

Top level context has become:

  • "without allow_list"
    • (all other cases)
  • "with allow_list"
    • (all other cases)

I don't feel that "allow_list" is such an important feature to warant top ranking in testsuit hierarchy. Can we append new testsuit for new feature to previous hierarchy? Example

  • (all other cases)
  • #allow_list
    • without allow_list
      • (tests)
    • with allow_list
      • (tests)

Copy link
Author

Choose a reason for hiding this comment

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

what do you think to something like:

  • without any configurations
  • with configurations
    • #allow_list

I put all the other cases inside a context to be sure that the setup for those other cases does not pollute the test scenarios for allow list and vice versa. If I just remove the context and let all other cases direct in the top level, it will run a lot of setup code on before for the allow list tests that are not necessary, won't be useful and can generate unintended results. Even thinking about test performance, adding more options, and having this top-level cases running every time is not good.

I considered writing the validations tests(allow list now, and maybe a block list, quantity limit, and more in the future) in a different file, so we would have all normal and top case scenarios here in this file, and, for now, allow list tests in another file. What do you think of this approach?

@jplethier
Copy link
Author

jplethier commented Jun 28, 2022

@skatkov thanks a lot for all the feedback and suggestions. I did some of them already, and I am in doubt about the others ones, that I answered you here.

@jplethier jplethier requested a review from skatkov June 28, 2022 23:53
Copy link
Collaborator

@skatkov skatkov left a comment

Choose a reason for hiding this comment

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

Nice work so far. I don't have too much time to contribute to open source these days.

So while kid is sleeping, decided to give this PR a review. If we're planning to change API of this gem, i'd love to polish it up more!

I've shared some of my thoughts on proposed changes. Some of those thoughts are pretty blury :P

@@ -2,14 +2,24 @@

module ActsAsTaggableArrayOn
module Taggable
class TaggableError < StandardError; end

class InvalidAllowListTypeError < TaggableError
Copy link
Collaborator

@skatkov skatkov Jul 3, 2022

Choose a reason for hiding this comment

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

It's nice to see that your error handling, it's really useful.

I would like to consider if all of this code to raise an error is actually necessary. Maybe there is a way to simplify and improve API in here?

We basically have two additional errors right now, they are:

ActsAsTaggableArrayOn::Taggable::TaggableError
ActsAsTaggableArrayOn::Taggable::InvalidAllowListTypeError

Not really readable, though? "InvalidAllowListTypeError" reminds me of my java development experience, but we can do better in ruby :P

We use only one error in our code, though. Why do we define two types of errors?

One of errors has a .to_s method, but second one doesn't. Should we provide a way to translate this definition? Do we really need it?

What kind of errors do ActionRecord/ActiveModel throws? Would it be better to use that instead?

Copy link
Collaborator

@skatkov skatkov Jul 3, 2022

Choose a reason for hiding this comment

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

I've looked into some possible errors from ActiveRecord, and it seems reasonable to inherit from ActiveRecord::ActiveRecordError. But can we reuse one of errors from ActioveRecord namespace or should we define our own? I haven't found anything suitable for our cause in existing list off errors.

I would propose to use ActiveRecord namespace for our errors, e.g: And define a single error only.

class ActiveRecord::<error_name> <  ActiveRecord::ActiveRecordError; end

Regarding a name, I propose something simpler, maybe TagDefinitionError or maybe TaggableArrayError?

context "with allowed option" do
context "when options is not an array" do
it "raises an error" do
expect { User.acts_as_taggable_array_on :colors, allowed: "red, blue" }
Copy link
Collaborator

Choose a reason for hiding this comment

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

What should happen if allowed is an empty array? Can we test for that as well?

Copy link
Author

Choose a reason for hiding this comment

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

Good catch. What do you think about throwing an error in this scenario? Another option would be to allow any value, having the same behavior as when the allowed option is not present.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I allowed only has as an empty array, just act like there is no such setting. No need to throw errors.

expect(User).to respond_to(:without_all_colors)
end
end
context "without allowed option" do
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you please remove this context? I don't see any need for this to be in a root level context for testsuite.

Copy link
Author

Choose a reason for hiding this comment

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

Did you read my previous comment about this? What do you think about moving all validations tests scenarios to another file?

shared_examples "allowed with varchar array" do
it "accepts allowed values as strings" do
expect(User.new(colors: %w[blue red])).to be_valid
expect(User.new(colors: %w[red])).to be_valid
Copy link
Collaborator

Choose a reason for hiding this comment

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

Your testing for array with symbols, string or integer values. But what if anything else will be provided? A hash like object inside array? Any other object? class name?

Copy link
Author

Choose a reason for hiding this comment

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

I think that we could throw an exception in this case, what do you think?

Copy link
Author

Choose a reason for hiding this comment

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

I think that we can go forward now supporting only these three kinds of types, integers, strings and symbols, do you agree?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's start with just integers, strings, symbols inside an array.

It's probably possible to check if any other object is serializable, but let's not go that far for now.

def define_allowed_validations!(tag_name, allowed)
raise InvalidAllowListTypeError if !allowed.is_a?(Array)

remove_const "#{tag_name.upcase}_ALLOWED" if const_defined?("#{tag_name.upcase}_ALLOWED")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm afraid, that my previous comment about constant was incorrectly phrased and understood. Sorry about that.

Would be awesome not to make this impementation detail part of public api for this gem. Can we hide this allowed variable inside a private method?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not most used to this meta-programming method definitions in ruby, but I tried to apply your suggestion here d5c44f1

@jplethier
Copy link
Author

Nice work so far. I don't have too much time to contribute to open source these days.

So while kid is sleeping, decided to give this PR a review. If we're planning to change API of this gem, i'd love to polish it up more!

I've shared some of my thoughts on proposed changes. Some of those thoughts are pretty blury :P

I know the feeling, I have two kids, and most of work on open source or personal projects is done before they awake or after they go to bed. :D

Thanks a lot for the review and the suggestions.

@jplethier jplethier requested a review from skatkov July 13, 2022 11:28
@jplethier
Copy link
Author

@skatkov were you able to take a look in the changes that I did after your suggestions?

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.

2 participants