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
Related
I want to pass instance variables to a method, which then modifies them. This is because I have the same logic for different instance variables. Is this possible? I haven't got it working.
class A
def update_ivar()
update(#ivar)
end
def update(var)
var = 1
end
def print_ivar()
puts #ivar
puts #ivar == nil
end
end
a = A.new
a.update_ivar()
a.print_ivar()
Output
true
You can use instance_variable_set like this:
class A
def update_ivar
update(:#ivar) # Note the symbolized name here, it is not the variable itself
end
def update(var_name)
instance_variable_set(var_name, 1)
end
def print_ivar
puts #ivar
puts #ivar == nil
end
end
a = A.new
a.update_ivar
a.print_ivar
#=> 1
#=> false
I personally wouldn't like such a pattern because it leads to hard to read and understand code. But is it a code smell? That certainly depends on your application and your exact use case.
Watching Railscasts, more specifically Form Objects. Here is the code.
Controller code:
def create
#signup_form = SignupForm.new
if #signup_form.submit(params[:user])
session[:user_id] = #signup_form.user.id
redirect_to #signup_form.user, notice: "Thank you for signing up!"
else
render "new"
end
end
Method found on form object:
class SignupForm
def submit(params)
user.attributes = params.slice(:username, :email, :password, :password_confirmation)
profile.attributes = params.slice(:twitter_name, :github_name, :bio)
self.subscribed = params[:subscribed]
if valid?
generate_token
user.save!
profile.save!
true
else
false
end
end
end
I understand most of the code, but what I don't understand is how .valid? can run without an object written directly in front of it (i.e.: object.valid?)? I tried replicating this with Ruby, but Ruby requires an object to be directly written in front of the method, which leads me to believe this is some sort of Rails magic.
Can someone explain how .valid? runs without an object in front of it , and which object it picks up?
I tried using the following Ruby code and did not work:
array = [1,2,3,4]
def meth
if is_a?
puts "is array"
else
puts "not array"
end
end
array.meth => error: wrong number of arguments (given 0, expected 1)
In the Railscast #416 in question, Ryan includes (among others) the ActiveModel::Validations module into the SignupForm class. This module implements the valid? method for the class.
Now, in Ruby you can always call methods on the current object (i.e. self) without explicitly naming the receiver. If the receiver is unnamed, self is always assumed. Thus, in your submit method, valid? in called on the same instance of the SubmitForm where you originally called submit on.
class SignupForm
def submit(params)
user.attributes = params.slice(:username, :email, :password, :password_confirmation)
profile.attributes = params.slice(:twitter_name, :github_name, :bio)
self.subscribed = params[:subscribed]
if valid?
generate_token
user.save!
profile.save!
true
else
false
end
end
def valid? // <--- this is what they are calling.
return true // this is made up... i am sure it does something
end
end
I need an api key to save a user, and I need a user_id to save an api_key... Can I do both at once?
user.api_key = ApkiKey.generate_token
user.save
user.api_key.user_id = user.id
user.api_key.save
If the api_key has belongs_to relationship with user then following will work
user.api_key = ApkiKey.generate_token
user.api_key.user_id = user.id
user.save
the user.save will also trigger the user.api_key.save
I ended up doing the following:
#api_key.rb
before_create :generate_access_token
def generate_access_token
begin
self.access_token = SecureRandom.hex
end while self.class.exists?(access_token: access_token)
end
#user.rb
before_create do |user|
user.api_key = ApiKey.create(user_id: user.id)
end
The problem was that I didn't think I could access user.id before I created the user, but apparently it works. Thanks for the heads up #Hardik
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
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"]