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"]
Related
Hi!
I am expecting #<PrettyThing:0x0055a958175348 #success="anything here">
but I am getting 'anything here' instead. Any idea why?
class Thing
attr_accessor :success
def execute
self.success = execute!
rescue
self.success = false
ensure
self
end
end
class PrettyThing < Thing
def execute!
'anything here'
end
end
p PrettyThing.new.execute # => 'anything here'
Ensure is a tricky thing. Normally, it doesn't return a value, and instead the return value from the last executed line of the main or rescue block is returned, unless there was an uncaught error, then the error is returned. But, if you explicitly return, then you will get the return value. This is a bit nonstandard and confusing though, because the intent of the ensure clause is for silent cleanup. It may be better to move your return value outside your begin/rescue block.
Try:
class Thing
attr_accessor :success
def execute
self.success = execute!
self
rescue
self.success = false
end
end
class PrettyThing < Thing
def execute!
'anything here'
end
end
p PrettyThing.new.execute # => <PrettyThing:0x0000000379ea48 #success="anything here">
The way you have it written, execute is returning the assignment result of self.success = execute!. By adding self, you return the instance of PrettyThing.
This is handy if you want to chain methods, like:
class Thing
attr_accessor :success
def execute
self.success = execute!
self
rescue
self.success = false
end
def foo
puts 'foo'
end
end
class PrettyThing < Thing
def execute!
'anything here'
end
end
p PrettyThing.new.execute.foo # => foo
Given your comment, I think I'd probably do it something more like:
class Thing
attr_accessor :success
alias success? success
def foo
puts 'foo'
end
end
class PrettyThing < Thing
def execute
#success = everything_worked
self
end
private
def everything_worked
# your logic goes here
# return true if all is good
# return false or nil if all is not good
true
end
end
pretty_thing = PrettyThing.new.execute
p pretty_thing.success? # => true
If everything_worked returns false or nil, then pretty_thing.success? will also return false or nil.
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
I have a Company model with attr_accessor :administrator, so when user creates company, he also need to fill some fields for administrator of this company. I'm trying to test, that he fill all fields correctly.
class Company < ActiveRecord::Base
attr_accessor :administrator
validates :name, presence: true
validates :administrator, presence: true, if: :administrator_is_valid?
private
def administrator_is_valid?
administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]
end
end
company_spec.rb is:
require 'rails_helper'
describe Company do
it 'is valid with name and administrator' do
company = Company.new(name: 'Company',
administrator: {
name: nil,
email: nil,
phone: nil,
password: 'password',
password_confirmation: ''
})
expect(company).to be_valid
end
end
So, as you see, I have a lot of mistakes in validation test, but RSpec pass it.
Thanks!
That's because you didn't construct your validation properly. See, if: administrator_is_valid? will return false for your test, telling Rails to skip this validation rule.
I suggest you drop using the presence validator in favor of using administrator_is_valid? method as a validation method, because after all, if the administrator is valid then it is present. The code should look like this
validate :administrator_is_valid?
private
def administrator_is_valid?
(administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]) or
errors.add(:administrator, 'is not valid')
end
You could clean up your code like this:
validate :administrator_is_valid?
private
def administrator_is_valid?
if administrator_cols_present? && administrator_passwords_match?
true
else
errors.add(:administrator, 'is not valid')
end
end
def administrator_cols_present?
%w(name phone email password password_confirmation).all? do |col|
administrator[col.to_sym].present? # or use %i() instead of to_sym
end
end
def administrator_passwords_match?
administrator[:password] == administrator[:password_confirmation]
end
Another improvement might be to move your administrator to a struct, then call valid? on the object.
admin = Struct.new(cols) do
def valid?
cols_present? && passwords_match?
end
def cols_present?
cols.values.all? { |col| col.present? }
end
def passwords_match?
cols[:password] == cols[:password_confirmation]
end
end
Then:
validate :administrator_is_valid?
def admin_struct
#admin_struct ||= admin.new(administrator)
end
def administrator_is_valid?
errors.add(:administrator, 'is not valid') unless admin_struct.valid?
end
I have a very specific situation where I want to force an instance of a model not valid.
Something like this:
user = User.new
user.valid? #true
user.make_not_valid!
user.valid? #false
Any way to achieve that?
Thanks!
You can do:
validate :forced_to_be_invalid
def make_not_valid!
#not_valid = true
end
private
def forced_to_be_invalid
errors.add(:base, 'has been forced to be invalid') if #not_valid
end
Another variant that I found useful for testing:
invalid_instance = MyModel.new
class << invalid_instance
validate{ errors.add_to_base 'invalid' }
end
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