How to have a maintainable username blacklist - ruby-on-rails

My app is a social platform and I want to have the support-team being able to maintain a blacklist for nicknames. Therefore I have a model Blacklist that contains the different names.
Now I tried with the validates_exclusion_of to map these into the in: but it raises the following error,
wrong number of arguments (given 1, expected 0)
validates_exclusion_of :nickname, in: -> {
where(Blacklist.select(:name).map(&:name).uniq.to_s)
}, message: "This nickname is not allowed"

You don't need to wrap the values to exclude within a lambda, it'll work as is if you pass just the Blacklist AR query result.
validates_exclusion_of :nickname,
in: Blacklist.select(:name).map(&:name).uniq.to_s,
message: 'This nickname is not allowed'
Notice, you can use the ActiveRecord::Calculations#pluck in order to get just the names from Blacklist, getting rid of the select and map combination, and use ActiveRecord::QueryMethod#distinct to get non-repeated values.
With that you don't need the uniq and to_s step. The last one, because the validation is waiting for an enumerable object, and you'd be passing a string.
Something like:
validates_exclusion_of :nickname,
in: Blacklist.distinct.pluck(:name),
message: 'This nickname is not allowed'
The where you're using on the validation won't work, as it's applying a WHERE statement without passing a column name to check, so you'll get an ActiveRecord::StatementInvalid.

Related

Declare a required array parameter using Rails Strong Params

In my Rails controller I'd like to require a single parameter, emails. It must be an array.
This is allowed.
emails[]=foo#bar.com&emails[]=up#down.com
This is not allowed.
emails=foo#bar.com
params.require(:emails) will allow both. The former comes as an Array. The latter as a String. This is a problem.
params.permit(emails: []) does not require an emails parameter.
params.require(:emails).permit([]) does not work because params.require(:emails) gets clever and returns an Array, not an ActionController::Parameters.
How do I allow emails[]=... and disallow emails=...?
Like what tadman said, Screening is what Strong Parameters does mostly.
You can look into Grape, perhaps it can show you some directions for your concerns
desc "API demo", {}
params do
requires :id, type: Integer, desc: 'ID'
requires :name, type: Boolean, desc: 'xxx'
end
Error msg would be received if you pass it an unexpected type of parameters

How to write a graphQL mutation that allows for partial updates?

So I'm working on trying to learn GraphQL for ruby for a project.
I've almost got some parts of it up and running, but I'm having issues with other parts. There are plenty of tutorials out there that cover ultra-basics, but none of them seem to expand in the right directions.
I have a mutation to update my user. So far so good. I can look up the user by their ID, and update a single specific field. I can extend that to updating two fields.
What I cannot do, and this is looking insane, is generalize those fields -- at all. My user model will wind up with over 20 fields attached to it -- phone numbers, addresses, job title, etc etc.
When I create the mutation, I have to define the arguments that go into the resolve method. So far so good. I then define the fields the mutation can return. Again, so far so good.
Then I get to the actual resolve method.
The initial syntax isn't bad. def resolve(user_id:, name:, email:). Then you discover that despite setting required to false, you have to include all the values. You need to specify default values for the optional variables. So it becomes def resolve(user_id:, name: null, email: null) -- but that actually nulls out those values, you can't do partial updates. Worse yet, imagine having 20 fields you have to set this way. You can play games by trying to convert the arguments into a dictionary and rejecting null values -- but then you can't set properties to nil if they need to be nil again.
The solution: a double splat operator. Your syntax becomes def resolve(user_id:, **args). From what I can tell, it turns all remaining named arguments into a dictionary -- and I think unnamed arguments would become an array. Not sure how it would react with a mix of the two.
Full model becomes:
argument :user_id, ID, required: true#, loads: Types::UserType
argument :name, String, required: false
argument :email, String, required: false
field :user, Types::UserType, null: true
field :errors, Types::UserType, null: true
def resolve(user_id:, **args)
user = User.find(user_id)
if user.update(args)
{
user: user,
errors: []
}
else
{
user: nil,
errors: user.errors.full_messages
}
end
end
end

Which takes precedence: Rails type casting or validation?

I am very curious about what this expected behavior is for Rails 4.2 and I have not been able to find an answer to my question.
I'm adding validation to a model on all the time attributes. I want them to ONLY accept integers, not numerical strings. The data type in my schema for this attribute is an integer. I have my validation like so:
RANGE = 0..59
validates :start_minute, inclusion: { in: RANGE }, numericality: true
I've tried these other validations as well. I get the same result.
validates_numericality_of :start_minute, inclusion: { 0..59, only_integer: true }
validates :start_minute, inclusion: { in: 0..59 }, numericality: { only_integer: true }
When I pass my params to my controller from the request spec, start_minute is "12". BUT when I look at the created object, the start_minute is 12.
According to this article by ThoughtBot:
"This is because Active Record automatically type casts all input so that it matches the database schema. Depending on the type, this may be incredibly simple, or extremely complex."
Shouldn't the object not be able to be created? Is the typecasting taking precedence of my validation? Or is there something wrong with my validation? I appreciate any insight to this as I haven't been able to determine what is happening here. I've also created a model spec for this and I'm still able to create a new object with numerical strings.
Thank you for any insight you can give on this. I am still learning the magic of Rails under the hood.
From the rails docs it says,
If you set :only_integer to true, then it will use the
/\A[+-]?\d+\z/
What it(only_integer validator) does is that it validates that the format of value matches the regex above and a string value that contains only numbers like '12' is a match(returns a truthy value which is 0 and passes the validation).
2.3.1 :001 > '12' =~ /\A[+-]?\d+\z/
=> 0

How do I use the value of an attribute within a model? Ruby on Rails

Basically, I have a model, Degree, and it has three attributes: degree_type, awarded_by, and date_awarded.
There are two arrays of values that should be valid for awarded_by. The two valid values for degree_type are "one" and "two", and the valid values for awarded_by depend on "one" and "two".
If degree_type is "one" (has a value of "one", that a user would put in), I want the valid values for awarded_by to be array_one. If degree_type has a value of "two", I want the valid values for awarded_by to be array_two.
Here is the code so far:
class Degree < ActiveRecord::Base
extend School
validates :degree_type, presence: true,
inclusion: { in: ["one",
"two"],
message: "is not a valid degree type"
}
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(awarded_by_type) }
end
Degree.schools outputs an array depending on the degree type, so Degree.schools("one") would return array_one, where
array_one = ['school01', 'school02'...]
My problem is, I don't know how to access the value of degree_type within the model.
What I tried below doesn't work:
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(:degree_type) }
I tried using before_type_cast but I was either using it incorrectly or there was another problem, as I couldn't get that to work either.
When I test this I get:
An object with the method #include? or a proc, lambda or symbol is required, and must be supplied as the :in (or :within) option of the configuration hash
Help me out? :) If any more info is needed, let me know.
EDIT: To add to this, I double checked it wasn't my Degree.schools method acting up - if I go into the rails console and try Degree.schools("one") or Degree.schools("two") I do get the array I should get. :)
EDIT again: When I tried #Jordan's answer, I got errors in the cases where the awarded_by was incorrect because in those cases, valid_awarded_by_values was nil and there is no include? method for a nil object. Therefore I added an if statement checking for whether valid_awarded_by_values was nil or not (so as to return if it was), and that solved the problem!
I put this inside the method, before the unless statement and after the valid_awarded_by_values declaration:
if valid_awarded_by_values.nil?
error_msg = "is not a valid awarded_by"
errors.add(:awarded_by, error_msg)
return
end
The easiest way will be to write a custom validation method, as described in the Active Record Validations Rails Guide.
In your case, it might look something like this:
class Degree < ActiveRecord::Base
validate :validate_awarded_by_inclusion_dependent_on_degree_type
# ...
def validate_awarded_by_inclusion_dependent_on_degree_type
valid_awarded_by_values = Degree.schools(degree_type)
unless valid_awarded_by_values.include?(awarded_by)
error_msg = "must be " << valid_awarded_by_values.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
errors.add(:awarded_by, error_msg)
end
end
end

Set column value based on email address regex?

My Rails application is targeted at college students -- only Berkeley and University of Washington students can sign up right now, and I use a regex to do it. The user model has a column in it called "school" that I'd like to fill with the name of their school based on their email address. Here's an example of what the validation might look like in my User.rb file (with just Berkeley):
berkeley_regex = /\A[\w+\-.]+#berkeley\.edu\z/i
validates :email, :presence => true, :format => {:with => berkeley_regex}
In the User controller, how could I go about setting the #user.school column to be Berkeley, for example, or University of Washington if I add a regex to recognize that email domain?
Move the regex matching into a method.
You could associate the regex with a key in a hash, too, making this easier, roughly:
EMAILS = {
"Berkeley": /\A[\w+-.]+#berkeley.edu\z/i,
"Washington": /whatever/
}
Iterate over the hash to get the value and regex:
EMAILS.each do |school, regex|
# If it matches, return the `school` value
end
Use the same method for your email validation by validating :email against the method instead of a simple regex as detailed in the Custom Validation section of the Active Record Validations Guide. You may need to tweak the return value etc. to get the function to work for both things, or write a thin wrapper for the validation around the lookup.
Consider a callback to actually set the value, e.g., :after_validation or :before_save.
First, create a new method that returns Berkeley or U of Washington depending on which regex matches.
Next, you will want to do an update on the user so:
user_school = find_what_school_user_goes_to(#user.email)
#user.update(:school user_school)

Resources