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

Add in ability to dynamically load in deny_listed domains #249

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

scouttyg
Copy link

@scouttyg scouttyg commented Aug 21, 2024

Working off of #222, I wanted to add in the ability to load in deny-listed domains from other places outside a deny_listed_email_domains.yml file.

This new feature should support:

  • Arrays
  • Sets
  • Procs
  • ActiveRecord queries
  • Boolean (TrueClass/FalseClass) support for backwards compatibility

Let me know what you think!

Comment on lines +25 to +56
class TestDynamicDomainModel
def self.where(*); end

def self.column_names
[domain_attribute].compact
end

def self.exists?(hash)
value = hash[self.domain_attribute.to_sym]
return false if value.nil?

existng_array = self.domain_attribute_values
existng_array.include?(value)
end

def self.domain_attribute
@domain_attribute ||= "domain"
end

def self.domain_attribute_values
@domain_attribute_values ||= []
end

def self.domain_attribute=(new_domain_attribute)
@domain_attribute = new_domain_attribute
@domain_attribute_values = domain_attribute_values
end

def self.domain_attribute_values=(new_domain_attribute_values)
@domain_attribute_values = new_domain_attribute_values
end
end
Copy link
Author

Choose a reason for hiding this comment

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

This is kind of a fake class we can use to test the hash for setting an active record query

spec/valid_email2_spec.rb Outdated Show resolved Hide resolved
@scouttyg
Copy link
Author

Small bump here @micke -- Let me know what you think!

@micke
Copy link
Owner

micke commented Aug 31, 2024

Thank you for your work and sorry for taking time to review this PR, I've been super busy with work and haven't had any time to spare for this.

I can appreciate that you've implemented a nice extendable API, but i'm sorry i don't see how the added complexity makes it worth it.
In my opinion we could achieve the same result by allowing the user to supply a proc to allow_list and deny_list that takes the domain as a argument and returns true or false.

@scouttyg
Copy link
Author

scouttyg commented Sep 3, 2024

@micke You'll need to outline your potential solution more, and perhaps I can refactor this to support that, as I'm not quite sure what you mean.

E.g. if we want to support a dynamically generated domain list (a supplied array, a query from ActiveRecord, etc), somewhere we'll need to provide the list of dynamically generated domains, so we could potentially just do a proc like:

validates :email, 'valid_email_2/email': { deny_list: proc { |domain_list, domain| domain_list.include?(domain) } }

But this proc would take at the very least two arguments: the domain_list (which we can dynamically generate / supply), and a domain -- but since we can only get domain from ValidEmail2::Address.new(email).address.domain (unless we want to parse it out using separate logic ourself at validation time), the logic would have to look something like:

validates :email, 'valid_email_2/email': { deny_list: proc { |domain_list, domain| domain_list.include?(ValidEmail2::Address.new(self.email).address.domain) } }

Is this what you were thinking?

@micke
Copy link
Owner

micke commented Sep 3, 2024

@scouttyg I'm sorry if i'm unclear.

What I'm trying to say is that i appreciate that you've made a nice API to support multiple types of lists, but the way i see it we can achieve that by accepting a proc like this:

validates :email, 'valid_email_2/email': { deny_list: ->(domain){ ["google.com"].include?(domain) } }
validates :email, 'valid_email_2/email': { deny_list: ->(domain){ DeniedDomain.where(domain:).exists? } }

Or we could also simplify it further by supporting:

  • proc that returns a boolean
  • Enumerable (by using the #include? method)

That would enable something like this:

validates :email, 'valid_email_2/email': { deny_list: ["google.com"] } }
validates :email, 'valid_email_2/email': { deny_list: Set["google.com"] } }
validates :email, 'valid_email_2/email': { deny_list: ->(domain){ DeniedDomain.where(domain:).exists? } }

In both versions we would parse the domain and pass it to the proc as the only argument or call #include?(domain) on it if it's a Enumerable.

@scouttyg scouttyg force-pushed the scott/add-in-dynamic-email-parsing branch 2 times, most recently from 88300ec to 909c3ec Compare September 3, 2024 21:41
@scouttyg scouttyg force-pushed the scott/add-in-dynamic-email-parsing branch from 909c3ec to 511c3ba Compare September 3, 2024 21:42
@scouttyg scouttyg changed the title Add in ability to dynamically load in blacklisted domains Add in ability to dynamically load in deny_listed domains Sep 3, 2024
@scouttyg
Copy link
Author

scouttyg commented Sep 3, 2024

@scouttyg I'm sorry if i'm unclear.

What I'm trying to say is that i appreciate that you've made a nice API to support multiple types of lists, but the way i see it we can achieve that by accepting a proc like this:

validates :email, 'valid_email_2/email': { deny_list: ->(domain){ ["google.com"].include?(domain) } }
validates :email, 'valid_email_2/email': { deny_list: ->(domain){ DeniedDomain.where(domain:).exists? } }

Or we could also simplify it further by supporting:

  • proc that returns a boolean
  • Enumerable (by using the #include? method)

That would enable something like this:

validates :email, 'valid_email_2/email': { deny_list: ["google.com"] } }
validates :email, 'valid_email_2/email': { deny_list: Set["google.com"] } }
validates :email, 'valid_email_2/email': { deny_list: ->(domain){ DeniedDomain.where(domain:).exists? } }

In both versions we would parse the domain and pass it to the proc as the only argument or call #include?(domain) on it if it's a Enumerable.

@micke Hmmm, I think the main challenges would lie with the first two cases you list in the last example above, as they are not procs/lambdas:

validates :email, 'valid_email_2/email': { deny_list: ["google.com"] } }
validates :email, 'valid_email_2/email': { deny_list: Set["google.com"] } }

Because of this, if we are just relying on the "truthiness" of the object, later on we'll still need to inspect that object to determine how to deal with it (e.g. if it's an Enumerable, use include?, if it's a TrueClass used the supplied yml list in the gem, otherwise raise InvalidInputError or something like that), or else deny_listed? within ValidEmail2::Address won't know how to deal with it, right?

If we are just mainly supporting procs/lambdas, however, that would make more sense, as we could basically just do:

# In ValidEmail2::Address
def deny_listed?
  valid? && (domain_is_in?(ValidEmail2.blacklist) || deny_list_lambda.call(address.domain))
end

This would still require though that we pass probably a good amount of the options eventually down to address though, as we'd need to use them if they were potentialy lambdas/procs.

Is this more in line with what you are thinking?

@scouttyg
Copy link
Author

Hey @micke, I've tried to simplify things down a bit, while still doing some validation on the procs / lambdas to make sure things are correct. Let me know what you think, and if this moves us in the right direction!

@phoet
Copy link
Contributor

phoet commented Nov 6, 2024

i think a straight forward simple and flexible API would be to use a method-name symbol { deny_list: :check_stuff_in_db } and pass the relevant info to that method on invocation.

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.

3 participants