-
Notifications
You must be signed in to change notification settings - Fork 26
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
base: master
Are you sure you want to change the base?
Conversation
Thanks for your contribution. Can you please rebase your changes and update README accordingly? |
New updates
…s inclusion on this list
a310511
to
0c19da7
Compare
@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) |
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've noticed, that your adding an .error
to AR object. Maybe it's good to test that? and make sure that .valid?
returns "false"?
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 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?
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.
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.
|
||
context "when options is an array" do | ||
before do | ||
User.acts_as_taggable_array_on :colors, allow_list: %w[red blue] |
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.
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'}
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.
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?
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.
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
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.
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? |
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 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.
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 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 |
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.
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)
- without allow_list
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.
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?
…ating tags allowed
@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. |
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.
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 |
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'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?
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'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" } |
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.
What should happen if allowed is an empty array? Can we test for that as well?
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 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.
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 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 |
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 you please remove this context? I don't see any need for this to be in a root level context for testsuite.
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.
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 |
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.
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?
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 think that we could throw an exception in this case, what do you think?
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 think that we can go forward now supporting only these three kinds of types, integers, strings and symbols, do you agree?
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.
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") |
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'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?
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'm not most used to this meta-programming method definitions in ruby, but I tried to apply your suggestion here d5c44f1
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. |
@skatkov were you able to take a look in the changes that I did after your suggestions? |
Proposal
This PR proposes to have a easy way to add simple validations for tags allowed to model using this gem
Changes