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

Array of enums #52

Open
23tux opened this issue Dec 15, 2019 · 9 comments
Open

Array of enums #52

23tux opened this issue Dec 15, 2019 · 9 comments
Labels
help wanted Extra attention is needed

Comments

@23tux
Copy link

23tux commented Dec 15, 2019

Hi! We often have the situation where a model has an array of strings that needs to be validated to only include certain values, and we need some predicate methods to check if a model has a certain value inside this array:

class User < ApplicationRecord
  after_initialize { self.roles = Array.wrap(roles) }

  ROLES = %w[admin user reporter].freeze
  validate do
    errors.add(:roles, :invalid_roles) if (roles - ROLES).any?
  end

  ROLES.each do |role|
    define_method("#{role}?") do
      roles.include?(role)
    end
  end
end

And IMHO, that looks suspiciously like a StoreModel use case: An array of enums. Only difference would be that a wrong role would raise an exception instead of a validation error (but in my case this would be ok).

As far as I can tell, this isn't possible right now with StoreModel, am I right? Would it be a feature you would consider to include? I'd be happy to try for a PR if you could give me some hints how to implement a feature like this.

@DmitryTsepelev
Copy link
Owner

Hi, @23tux!

Right now StoreModel focuses on isolating JSON(B) data from the parent model. In your example an array (where it is stored?) is used inside the ApplicationRecord subclass. Probably custom Rails validator will help here:

class User < ApplicationRecord
  ...
  validates :roles, array: { inclusion: { in: ROLES }
end

@flvrone
Copy link

flvrone commented Dec 17, 2019

I guess that enumerize gem could deal with it. I didn't try though :)

@23tux
Copy link
Author

23tux commented Dec 18, 2019

@DmitryTsepelev #roles is a JSON column on the users table. I thought because you have support for enum types on single attributes (in the example here https://github.com/DmitryTsepelev/store_model/blob/master/docs/enums.md it is the key status), it would be a good fit to extend this to support an array of enums.

Consider this example, maybe it's clearer this way:

class Configuration
  include StoreModel::Model

  enum :role, %i[admin user reporter], default: :user
end

class User < ApplicationRecord
  attribute :configuration, Configuration.to_type
end

My user has a configuration with an enum of role that defaults to :user. What if I want multiple roles for a user? I thought an array of enums would be a good solution for this. What do you think?

@DmitryTsepelev
Copy link
Owner

Aha, in this case having the built-in inclusion validator for arrays makes sense to me!

@23tux
Copy link
Author

23tux commented Dec 18, 2019

@FunkyloverOne you are right, enumerize has built in support for Arrays when you serialize them or use mongodb. I still have to find out if it works with json array columns though. But because we already have the store_model gem in our Gemfile, adding another dependency for this simple use case isn't something I'm very happy about

@23tux
Copy link
Author

23tux commented Dec 18, 2019

@DmitryTsepelev How would you implement this? If you can give me a push in the right direction, I'm happy to try for a PR.

@DmitryTsepelev
Copy link
Owner

@23tux This is what should be done to make it work:

  1. Take a look at docs about custom validators
  2. Add a new custom validator (we already have one) called ArrayValidator
  3. Implement the validator (there is a good example)
  4. Add specs
  5. Bonus point: add docs 🙂

As a result, it will be possible to add a validation to the StoreModel in a following way:

class Configuration
  include StoreModel::Model
  ROLES = %i[admin user reporter]
  enum :role, ROLES, default: :user
  validates :roles, array: { inclusion: { in: ROLES }
end

class User < ApplicationRecord
  attribute :configuration, Configuration.to_type
end

Bonus point: it might be helpful to have a shortcut for the enum method to use this validation by default enum :role, ROLES, default: :user, validate: true

@23tux
Copy link
Author

23tux commented Dec 26, 2019

@DmitryTsepelev thanks for the kick off!

I'm not sure if I understand the need of a validator when using enums. The current implementation raises an error when an invalid value is assigned, e.g. from your examples

class Configuration
  include StoreModel::Model

  enum :status, %i[active archived], default: :active
end

config = Configuration.new
config.status = "foo"
# ArgumentError: invalid value 'foo' is assigned
# from /usr/local/bundle/gems/store_model-0.7.0/lib/store_model/types/enum_type.rb:56:in `raise_invalid_value!'

I would suggest to keep the same behaviour for an array of enums: When you push an invalid value into the array, it should raise an error. Do you know what I mean?

@DmitryTsepelev
Copy link
Owner

@23tux Sorry for the long response, that's a valid point! We don't need to use a validator in the current implementation (however, there is a chance that someone would need to make this behaviour optional in the future)

@DmitryTsepelev DmitryTsepelev added the help wanted Extra attention is needed label Aug 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants