In my Rails app I want to validate the filter and post_type params.
Both are optional, but if they are present they must have a value and must have a value that matches one in an array of valid values.
In my controller I have two methods for checking them:
def validate_filter
if params.has_key?(:filter)
if params[:filter].present?
if ['popular', 'following', 'picks', 'promoted', 'first-posts'].include?(params[:filter])
return true
else
return false
end
else
return false
end
else
return true
end
end
def validate_post_type
if params.has_key?(:post_type)
if params[:post_type].present?
if ['discussions', 'snaps', 'code', 'links'].include?(params[:post_type])
return true
else
return false
end
else
return false
end
else
return true
end
end
And then in my main controller method I do:
def index
raise ActionController::RoutingError.new('Not Found') unless validate_filter && validate_post_type
...
So this means post_type= and post_type=cam would return a 404 but post_type=snaps would return true.
Is there a better way to validate that the params passed are valid but for both empty and if the key itself exists. Using just blank? and present? is not enough in this scenario.
I would probably move this logic to the model, but if you really want it in the controller you could simplify it.
def validate_filer
return true unless params.has_key?(:filter)
['popular', 'following', 'picks', 'promoted', 'first-posts'].include?(params[:filter])
end
In a case of an API I would consider letting the client know, that there is a validation error rather than just saying 404.
How about using ActiveModel::Validations?
class MyParamsValidator
include ActiveModel::Validations
AVAILABLE_FILTERS = %w(popular following picks promoted first-posts)
# this might come from an enum like MyModel.post_types
AVAILABLE_POST_TYPES = %w(discussions snaps code links)
attr_reader :data
validates :filter, inclusion: { in: AVAILABLE_FILTERS }, allow_blank: true
validates :post_type, inclusion: { in: AVAILABLE_POST_TYPES }, allow_blank: true
def initialize(data)
#data = data
end
def read_attribute_for_validation(key)
data[key]
end
end
class MyController < ApplicationController
before_action :validate_params, only: :index
def validate_params
validator = MyParamsValidator.new(params)
return if validator.valid?
render json: { errors: validator.errors }, status: 422
end
end
You can find more info about a nested case with some tests here.
You can do this in before_action callback of the controller.
before_action :validate_params, only: [:index]
def validate_params
return false unless params[:filter].present? && params[:post_type].present?
params_include?(:filter, %w(popular following picks promoted first-posts))
params_include?(:post_type, %w(discussions snaps code links))
end
Perhaps a small helper method:
def validate_filter
params_include?(:filter, %w(popular following picks promoted first-posts))
end
def validate_filter
params_include?(:post_type, %w(discussions snaps code links))
end
def params_include?(key, values)
!params.key?(key) || values.include?(params[key])
end
It is not clear from your question where that params are coming from, if they are query parameters or part of the path. If they are part of the path you might consider using routing constraints in your routes.rb
Related
There is a Request model in my app. On different pages I need different validations, for example on /contacts I need to validate a lot of fields, whereas in a 'call me back later' popup I need to validate only phone number and name.
My problem is: data is saved, but without validations and type is not saved aswell.
Structure:
request.rb
class Request < ApplicationRecord
self.inheritance_column = :_type_disabled
def self.types
%w(ContactRequest CallMeBackRequest)
end
scope :contacts, -> { where(type: 'ContactRequest') }
scope :callmebacks, -> { where(type: 'CallMeBackRequest') }
end
routes.rb:
resources :contact_requests, only: [:new, :create], controller: 'requests', type: 'ContactRequest'
resources :call_me_back_requests, only: [:new, :create], controller: 'requests', type: 'CallMeBackRequest'
contact_request.rb:
class ContactRequest < Request
validates :name, :phone, :email, :company_name, presence: true
def self.sti_name
"ContactRequest"
end
end
call_me_back_request.rb:
class CallMeBackRequest < Request
validates :name, :phone, presence: true
def self.sti_name
"CallMeBack"
end
end
requests_controller.rb:
class Front::RequestsController < FrontController
before_action :set_type
def create
#request = Request.new(request_params)
respond_to do |format|
if #request.save
format.js
else
format.js { render partial: 'fail' }
end
end
end
private
def set_request
#request = type_class.find(params[:id])
end
def set_type
#type = type
end
def type
Request.types.include?(params[:type]) ? params[:type] : "Request"
end
def type_class
type.constantize
end
def request_params
params.require(type.underscore.to_sym).permit(Request.attribute_names.map(&:to_sym))
end
end
My form starts with:
=form_for Request.contacts.new, format: 'js', html: {class: 'g-contact__sidebar-right g-form'}, remote: true do |f|
I tried using ContactRequest.new - result was the same.
What I get when I hit the console:
Request.contacts.create!(name: "something") - does get saved, no validations are applied (why?). No type field is populated - why?
ContactRequest.create!(name: "something") - does not get saved, validations are applied
ContactRequest.create!(name: ..., all other required fields) - does get saved, but field type is empty - why?
Whatever I use for my form - ContactRequest.new or Request.contacts.new - neither validations are applied nor field type is set correctly.
Can anyone point me in the right direction? I'm mainly using this tutorial and other SO question, but without success.
Figured it out - since I'm not using the dedicated pages and paths for those contacts, i.e. contact_requests_path and corresponding new.html.haml, I need to pass the type parameter as a hidden field.
So my form now looks like this:
=form_for ContactRequest.new, format: 'js', html: {class: 'g-contact__sidebar-right g-form'}, remote: true do |f|
=f.hidden_field :type, value: "ContactRequest"
Considering validations - I don't know what I did, but after restarting the server a few times, they work now. The only this I remember really changing was the sti name here:
class CallMeBackRequest < Request
validates :name, :phone, presence: true
def self.sti_name
"CallMeBack" <- changed it from "CallMeBack" to "CallMeBackRequest"
end
end
I want to implement a conditional update method on a specific set of values for a key.
I want to allow updates only if the original list.permissions values (set on create) equal either "public", "viewable, or "editable". If the list.permissions value for a record does not equal one of those three acceptable values, updating the record is denied (locked).
I tried modifying the strong params in a private method in the controller file:
def list_params_validated
params.require(:list).permit(:title, permissions: ["public", "viewable", "editable"])
end
and then calling that in my update method in the same controller:
def update
list = List.find(params[:id])
if list.update(list_params_validated)
render json: list
else
render json: { errors: list.errors.full_messages }, status: :unprocessable_entity
end
end
no luck with this, any help would be most appreciated!
You can simply do this on your controller
def list_params_validated
params.require(:list).permit(:title, :permissions)
end
and in your model simply add this
validates_inclusion_of :permissions, in: %w(public viewable editable), :on => :update, :message => "value %s is invalid!"
EDIT 1
To prevent updating the record, You should add before_update callback in your model as
before_update :locked?
def locked?
return false if YourModel.find(id).persmissions == 'locked'
true
end
You can add an custom validation on your List model as following and call simple update in controller.
self.permissions value should be string like "public" or "viewable" or "editable".
validate :validate_editable, :validate_permissions:on => :update
def editable?
self.permissions != "locked"
end
private
def validate_editable
errors.add(:base, "Not Ediable!") unless editable?
end
def validate_permissions
unless ["public", "viewable", "editable"].include?(self.permissions)
errors.add(:base, "Permission denied!")
end
end
I have a model:
class HelloRails < ActiveRecord::Base
attr_accessible :filename, :filevalidate
include In4systemsModelCommon
validates :filename, presence: true
def update
parameters = [self.filename, #current_user]
parameters.map!{|p| ActiveRecord::Base.connection.quote p}
sql = "call stored_procedure(#{parameters.join(',')})"
ActiveRecord::Base.connection.execute(sql)
end
end
In the view I have a text_field called as :filename. When I click the submit button it calls this update method in model to call the stored proc to execute. Now validations are not working.
I dont want to accept nil for filename. How can I do this?
It doesn't validate because you are executing sql directly:
ActiveRecord::Base.connection.execute(sql)
Validations are only run when you use the "normal" ActiveRecord methods like save and update_attributes. To run validations manually you can call valid? on your object.
Model:
def update
return false unless self.valid? # validation failed: return false
parameters = [self.filename, #current_user]
parameters.map!{|p| ActiveRecord::Base.connection.quote p}
sql = "call stored_procedure(#{parameters.join(',')})"
ActiveRecord::Base.connection.execute(sql)
true
end
Then in your controller you have to check wether #object.update returns true or false and display errors in your view if necessary.
Controller:
# This is just an example. I don't know your code.
def update
#object = HelloRails.find(params[:id])
if #object.update
redirect_to somewhere
else
render :action => 'edit'
end
end
So I want to do this because I think it is the most idiomatic way to do errors.
For example:
User < ActiveRecord::Base
def add_email?
...
#in the case that it does have an error
MyErrorObjectThatEvaluatesToFalse.new("The email is already taken")
end
def is_valid_user?
...
MyErrorObjectThatEvaluatesToFalse.new("Username is not set")
...
end
end
...
SomeController
...
if error = user.add_email?
render_error_msg(error.message) and return
elsif error = user.is_valid_user?
render_error_msg(error.message) and return
end
...
end
I've tried one of the solutions below, but it doesn't have the functionality that I would like:
class A
def ==(comp)
false
end
end
a = A.new
if a
puts "'a' evaluated to true"
else
puts "'a' evaluated to false"
end
#=> 'a' evaluated to true
Is there a way to do something like this or has some else found a way to handle errors that is better than the current rails way of indirectly getting the message with a combination of user.valid? and user.errors?
Thanks!
I would not recommend this as a method of validation, however to define a class that returns false on a comparator:
class A
def ==(comp)
false
end
end
A.new == "a" #false
A.new == true #false
A.new == false #false
A.new == nil #false
I would recommend using rails' built in validations.
class User < ActiveRecord::Base
validates :username, :presence => true
end
user = User.new
user.errors #["username must not be blank"]
I'm building a simple app and want to be able to store json strings in a db. I have a table Interface with a column json, and I want my rails model to validate the value of the string. So something like:
class Interface < ActiveRecord::Base
attr_accessible :name, :json
validates :name, :presence => true,
:length => { :minimum => 3,
:maximum => 40 },
:uniqueness => true
validates :json, :presence => true,
:type => json #SOMETHING LIKE THIS
:contains => json #OR THIS
end
How do I do that?
I suppose you could parse the field in question and see if it throws an error. Here's a simplified example (you might want to drop the double bang for something a bit clearer):
require 'json'
class String
def is_json?
begin
!!JSON.parse(self)
rescue
false
end
end
end
Then you could use this string extension in a custom validator.
validate :json_format
protected
def json_format
errors[:base] << "not in json format" unless json.is_json?
end
Currently (Rails 3/Rails 4) I would prefer a custom validator. Also see https://gist.github.com/joost/7ee5fbcc40e377369351.
# Put this code in lib/validators/json_validator.rb
# Usage in your model:
# validates :json_attribute, presence: true, json: true
#
# To have a detailed error use something like:
# validates :json_attribute, presence: true, json: {message: :some_i18n_key}
# In your yaml use:
# some_i18n_key: "detailed exception message: %{exception_message}"
class JsonValidator < ActiveModel::EachValidator
def initialize(options)
options.reverse_merge!(:message => :invalid)
super(options)
end
def validate_each(record, attribute, value)
value = value.strip if value.is_a?(String)
ActiveSupport::JSON.decode(value)
rescue MultiJson::LoadError, TypeError => exception
record.errors.add(attribute, options[:message], exception_message: exception.message)
end
end
The best way is to add a method to the JSON module !
Put this in your config/application.rb :
module JSON
def self.is_json?(foo)
begin
return false unless foo.is_a?(String)
JSON.parse(foo).all?
rescue JSON::ParserError
false
end
end
end
Now you'll be enable to use it anywhere ('controller, model, view,...'), just like this :
puts 'it is json' if JSON.is_json?(something)
I faced another problem using Rails 4.2.4 and PostgreSQL adapter (pg) and custom validator for my json field.
In the following example:
class SomeController < BaseController
def update
#record.json_field = params[:json_field]
end
end
if you pass invalid JSON to
params[:json_field]
it is quietly ignored and "nil" is stored in
#record.json_field
If you use custom validator like
class JsonValidator < ActiveModel::Validator
def validate(record)
begin
JSON.parse(record.json_field)
rescue
errors.add(:json_field, 'invalid json')
end
end
end
you wouldn't see invalid string in
record.json_field
only "nil" value, because rails does type casting before passing your value to validator. In order to overcome this, just use
record.json_field_before_type_cast
in your validator.
If you don't fancy enterprise-style validators or monkey-patching the String class here's a simple solution:
class Model < ApplicationRecord
validate :json_field_format
def parsed_json_field
JSON.parse(json_field)
end
private
def json_field_format
return if json_field.blank?
begin
parsed_json_field
rescue JSON::ParserError => e
errors[:json_field] << "is not valid JSON"
end
end
end
Using JSON parser, pure JSON format validation is possible. ActiveSupport::JSON.decode(value) validates value "123" and 123 to true. That is not correct!
# Usage in your model:
# validates :json_attribute, presence: true, json: true
#
# To have a detailed error use something like:
# validates :json_attribute, presence: true, json: {message: :some_i18n_key}
# In your yaml use:
# some_i18n_key: "detailed exception message: %{exception_message}"
class JsonValidator < ActiveModel::EachValidator
def initialize(options)
options.reverse_merge!(message: :invalid)
super(options)
end
def validate_each(record, attribute, value)
if value.is_a?(Hash) || value.is_a?(Array)
value = value.to_json
elsif value.is_a?(String)
value = value.strip
end
JSON.parse(value)
rescue JSON::ParserError, TypeError => exception
record.errors.add(attribute, options[:message], exception_message: exception.message)
end
end
The most simple and elegant way, imo. The top upvoted answers will either return true when passing a string containing integers or floats, or throw an error in this case.
def valid_json?(string)
hash = Oj.load(string)
hash.is_a?(Hash) || hash.is_a?(Array)
rescue Oj::ParseError
false
end