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
Related
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
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.
My delete api takes a boolean query params /users?force=true. In my controller, I added the following for apipie documentation
param :force, [true, false], required: false, desc: "query parameter."
When I make the api call, I get
Apipie::ParamInvalid (Invalid parameter 'force' value "true": Must be one of: <code>true</code>, <code>false</code>.).
I tried passing /users?force=1, /users?force, but 1 is treated as "1" and not passing anything is treated as nil and both calls fail. How do I make the validation pass?
Note: I am aware that the api definition is not restful.
The param you are passing through ends up being "true" as in string, not a boolean, that's why it's failing. Without any type casting, rails has got no idea that you're trying to pass boolean, not a string.
You should whitelist "true", "false" as strings in valid options like:
param :force, [true, false, "true", "false"], required: false, desc: "query parameter."
I want to create a route for the Grape gem so that a route accepts either an array of strings or a single string with a specific pre-defined value. From the documentation it's not clear how to do that.
Your suggestions?
UPDATE:
I want status to be passed either a single value status1 or as a array where values can be arbitrary and unknown. I combine these?
params do
requires :status, type: Symbol, values: [:status1]
requires :status, type: Array[String]
end
A parameter must be declared only once in the params block. If you declare it twice then only one will be used by Grape. In your case, there're two options to solve your problem.
First option: declare two parameters and define them as mutually exclusive. It means that the user will be able to inform only one of them.
params do
requires :status1, type: Symbol, values: [:status1]
requires :status2, type: Array[String]
mutually_exclusive :status1, :status2
end
Second option: declare only one parameter and set its type to Object. In the method's body, check if it's an Array or a String. If it's a String, verify if it has the correct values.
params do
requires :status, type: Object
end
get 'testing' do
if params[:status].class.name.eql? "Array" then
elsif params[:status].class.name.eql? "String" then
end
end
I have a hard time understanding the form :attribute => parameter
Can anyone give me some explanations for it? Is :attribute a field (variable) belonging to the class or something else? Why we can pass this pair as one parameter to methods?
If you're referring to something like this:
some_method(:foo => "bar", :baz => "abc")
then it's just shorthand which causes ruby to convert those things into a Hash. Please note that when using this form, that the hash must be the final argument to the method in order for this to work.
Based on the explanation above, this
some_method(:foo => "bar", :baz => "abc")
is ok, but this
some_method(:foo => "bar", :baz => "abc", moo)
is not.
Though you will see this commonly in Rails, it is not a Rails specific question. It is Ruby.
The answer to your question is that it is key/value pairs in a Hash, generally passed as an argument to a method.
You will see this as well when it is being assigned to a variable directly. But let me show you a sample method, and a sample usage, so that you can put them together:
def some_method(*args, name: 'Joe', amount: 42, **other_params )
puts "#{name}, #{amount}, glob of arguments = #{args.inspect}",
"other params #{other_params}"
end
some_method(:occupation => 'programmer', :phone => '123-456-7890', name: 'Jane')
This is Ruby 2.0.0 specific in the fact that you can provide for that last argument, which provides for unnamed parameters, in practice. Using the 1.9+ syntax for a Hash in the argument list, you are allowed to provide for other unnamed "parameters" which can appear after the hash argument.
Notice that if I had used the older syntax for Hash, namely the :key => 'value' syntax, I would not be allowed (at least as of this writing) to have the **other_params argument at the end of the argument list.
You could also provide the hash using the newer syntax in the calling code, though I left it as the Hash syntax when calling some_method.
The Hash still needs to be the last provided in the calling argument list, the same as indicated in the argument list for the method definition.