Ok.
1) I need to validate :link in my model and do that only if it is not blank (or nil).
2) If :link is not blank and standard validation passes — I need to run my custom validation method to check URL availability.
By "standard" validation I mean something like this:
validates :link, :presence => true, :uniqueness => true,
:format => { :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix }
What is the correct way to implement this?
It checks for validation in your model only if link isn't blank:
validates_presence_of :link, :uniqueness => true,
:format => { :with => /^(http|https)://[a-z0-9]+([-.]{1}[a-z0-9]+).[a-z]{2,5}(:[0-9]{1,5})?(/.)?$/ix }, :if => :link_present?
def link
self.link
end
def link_present?
link.present?
end
Ok. With friends help I finally solved this.
class Post < ActiveRecord::Base
# skipped some things not necessary
validates_format_of :link, :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix , :allow_blank => true
validates_length_of :link, :maximum => 2000
validates_uniqueness_of :link, :allow_blank => true
validate :ensure_link_is_available, :if => proc{|post| post.link.present? && post.errors.empty?}
def ensure_link_is_available
begin
require "net/http"
url = URI.parse(self.link)
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)
rescue
# error occured, add error
self.errors.add(:link, 'The requested URL could not be retrieved')
else
# valid site
if (res.code.to_i > 308)
error_message = 'Server responded with ' + res.code
self.errors.add(:link, error_message)
end
end
end
end
validates_format_of :url_field, :with => URI::regexp(%w(http https))
Related
I have a model that takes an email address, but I do not want to require it for instance creation. I tried the following:
validates :email, :presence => false, :format => { :with => email_regex }
But this fails my test suite, the format regex apparently makes the presence of the email attribute required.
I thought it might be possible with a before_save method: is it possible to reject saving the object in a before_save method? Is there a better, "Rails way" of doing this?
You can use :allow_blank (or :allow_nil):
validates :email, :allow_blank => true, :format => { :with => email_regex }
I'm having some trouble with lazy loading, i'm pretty sure of it ... maybe you could point out to me where I've failed.
def setup_guild
if params[:guild]
#guild = Guild.where(:short_name => params[:guild].upcase).first
if #guild.nil?
puts "no guild with short name #{params[:guild]} found"
redirect_to root_path
else
#title = t "layout.guild_title", :guild_name => (#guild.name).capitalize
end
else
#guild = nil
end
end
Which is called in ApplicationController as a before filter.
At first I used Guild.find_with_short_name, but I had the same dumb answer as now ... that is :
undefined method `capitalize' for nil:NilClass
app/controllers/application_controller.rb:29:in `setup_guild'
Which is, you'd guess the #title line up there.
The thing is, if I try something similar in the console I get the expected result
ruby-1.9.2-p0 > guild = Guild.where(:short_name => "ICPT").first
Guild Load (0.5ms) SELECT "guilds".* FROM "guilds" WHERE "guilds"."short_name" = 'ICPT' LIMIT 1
=> #<Guild id: 2, name: "Inception", detail: "Inception Guild", game_server_id: 2, created_at: "2011-10-30 17:41:19", updated_at: "2011-10-30 17:41:19", short_name: "ICPT">
ruby-1.9.2-p0 > guild.name.capitalize
=> "Inception"
More, if I put something like "puts #guild.inspect" right after the fetch, the capitalization works fine, hence I think it's lazy loading failure.
I'd be happy to have some idea as to how to solve that dumb problem ... I don't really want to have an #guild.inspect for nothing in my code, i find that to be lame solution ...
Thanks !
#PanayotisMatsinopoulos As requested, here is the table Guild :
create_table "guilds", :force => true do |t|
t.string "name"
t.text "detail"
t.integer "game_server_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "short_name"
end
#PanayotisMatsinopoulos Here you go my friend ;) I still have to i18n it
#encoding: utf-8
class Guild < ActiveRecord::Base
belongs_to :game_server
has_one :game, :through => :game_server
has_many :announcement, :dependent => :destroy
validates_presence_of :name, :on => :create, :message => "dois avoir un nom"
validates_presence_of :detail, :on => :create, :message => "dois avoir une description"
validates_presence_of :game, :on => :create, :message => "dois appartenir a un jeu"
validates_presence_of :short_name, :on => :create, :message => "dois avoir un acronyme"
validates_uniqueness_of :short_name, :on => :create, :message => "est deja utilise"
validates_length_of :short_name, :within => 3..5, :on => :create, :message => "dois faire entre 3 et 5 caracteres"
validates_exclusion_of :short_name, :in => %w( users admin guilds events loots sessions characters games password), :on => :create, :message => "ne peux pas utilisé se genre d'acronyme"
validates_uniqueness_of :name, :on => :create, :message => "est deja utilise"
has_many :guild_mates, :dependent => :destroy
has_many :guild_ranks, :dependent => :destroy
has_many :guild_settings, :dependent => :destroy
has_many :events, :dependent => :destroy
has_many :characters, :dependent => :destroy
before_validation :short_name_check ,:on => :create
after_create :guild_basic_settings
def guild_basic_settings
GuildSettingType.all.each do |grst|
grs = GuildSetting.create do |g|
g.guild_id = self.id
g.guild_setting_type_id = grst.id
g.value = "false"
end
end
set_setting(["setting_allow_basic_access_for_public","setting_allow_application",
"setting_allow_event_read_for_public","setting_allow_announcement_read_for_public"],"true")
end
def set_setting(setting,value)
if setting.class == Array
setting.uniq!
setting.each do |ar|
set_setting(ar,value)
end
else
grs = nil
if setting.class == String
grs = guild_settings.includes(:guild_setting_type).where(:guild_setting_type => {:name => setting}).first
return if grs.nil?
else
grs = guild_rank_settings.where(:guild_setting_type => setting)
return if grs.nil?
end
grs.value = value
grs.save
end
end
def short_name_check
short_name.upcase! if short_name
end
def full_name
"#{name.capitalize} - #{game_server.name}"
end
def characters_for_user(user)
characters.where(:user_id => user.id)
end
def method_missing(method,*args)
check = method.to_s.split("_")
if(args.count == 0)
if check[0] == "setting"
grs = guild_settings.joins(:guild_setting_type).where(:guild_setting => { :guild_setting_types => {:name => method.to_s}}).first
unless grs.nil?
return grs.value == "true" ? true : false
else
raise "Guild > Method Missing > unknown setting : #{method.to_s}"
end
end
end
end
end
Edit : I've just seen that i didn't super method missing ... might that be the problem ?
Okay it seems that the problem was my method_missing implementation. It was lacking super call ... Now that it has been restored, everything works fine. no wonder.
Thanks #PanayotisMatsinopoulos for your help :) (also thanks a good night of sleep ;p )
you should check if name is nil also:
if #guild.nil? || #guild.name.nil?
True. method_missing should call super at the end. But, I am not convinced that your problem is there. It may be. It may not.
On the other hand, let me tell something that I believe has more chances to be your problem. This is the fact that you carry out your validation on presence of name only :on => :create. This means that an update of an object Guild that does not contain the name will pass validation and will be saved in the database without problem. Then your setup_guild will definitely throw the error:
undefined method `capitalize' for nil:NilClass
app/controllers/application_controller.rb:29:in `setup_guild'
i.e. the error this discussion started about.
Hence, my suggestion is to remove your :on => :create condition on the validation of name. (an BTW...I suggest that you remove it from all your validations unless you know what you are doing)
But then, I cannot prove that this was your problem in the first place. I am just putting here my advice, rather than my positive answer as a solution to your problem.
I'm using a nested model form for sign-up and am working through the kinks as a beginner. One issue that popped up in particular though that I don't really get is user.email is returning as nil.
Before I started playing around with the nested model form, I could create records in the console wihtout a problem. Now, however I can't create records and some of the latest records created have nil as their email. (I'm not sure if it has anything to do with the nested model at all, but that's my reference point for when it started going haywire.)
If I go into rails console to create a new User/Profile, I follow this process:
user = User.new
user.email = ""
user.password = ""
user.profile = Profile.new
user.profile.first_name = ""
...
user.profile.save
user.save
Everything goes well until user.save, which gives me the NameError: undefined local variable or method 'params' for #<User:>. In rails console it pinpoints to user.rb:25 in create_profile
So here is my User model:
class User < ActiveRecord::Base
attr_accessor :password, :email
has_one :profile, :dependent => :destroy
accepts_nested_attributes_for :profile
validates :email, :uniqueness => true,
:length => { :within => 5..50 },
:format => { :with => /^[^#][\w.-]+#[\w.-]+[.][a-z]{2,4}$/i }
validates :password, :confirmation => true,
:length => { :within 4..20 },
:presence => true,
:if => :password_required?
before_save :encrypt_new_password
after_save :create_profile
def self.authenticate(email, password)
user = find_by_email(email)
return user if user && user.authenticated?(password)
end
def authenticated?(password)
self.hashed_password == encrypt(password
end
protected
def encrypt_new_password
return if password.blank?
self.hashed_password = encrypt(password)
end
def password_required?
hashed_password.blank? || password.present?
end
def encrypt(string)
Digest::SHA1.hexdigest(string)
end
end
Can anyone help me figure out what's going on?
UPDATE: I tried changing my regex but I'm still seeing nil for email. Though a prior SO post said not to blindly copy regex without testing, so maybe I just didn't test it correctly. Good news though: I no longer get the error.
attr_accessor simply defines a "property" on the object and has no relation to the attributes of a ActiveRecord model (attributes is a Hash of the fields and values obtained from a table).
ActiveRecord does not save such "properties" as defined by the attr_accessor. (Essentially, attr_accessor defines a attr_reader and attr_writer (i.e. "getter" and "setter") at the same time)
I have the following in my model:
validates :name, :if => :should_validate_name?,
:presence => {:message => "Enter your name"},
:length => { :maximum => 50 },
:allow_blank => true
def should_validate_name?
validating_name || new_record?
end
In my controller I have the following:
def create
#user = User.new(params[:user])
#user.validating_name = false
if #user.save
else
render :action => 'new'
end
end
I don't want to validate for the presence of a name at this point and wish to turn it off.
I thought the code above would work but it doesn't. I don't know why.
You're in the create action, creating a new record. So new_record? will be true, even if validating_name isn't.
I am implementing a validation scheme and am using the bcrypt-ruby gem.
require 'bcrypt'
class User < ActiveRecord::Base
include BCrypt
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
validates :password, :presence => true, :on => :create,
:confirmation => true,
:length => {:within => 6..12}
before_save :encrypt_password
def has_password?(submitted_password)
self.encrypted_password == submitted_password # this calls a method in bcrypt
# File lib/bcrypt.rb, line 171
# def ==(secret)
# super(BCrypt::Engine.hash_secret(secret, #salt))
# end
end
private
def encrypt_password
self.encrypted_password = Password.create(password, :cost => 5)
end
end
Now in the console I create a new user
>> user = User.create!(:name => "test", :email => "test#test.com", :password => "foobar", :password_confirmation => "foobar")
=> #<User id: 1, name: "test", email: "test#test.com", created_at: "2011-06-23 05:00:00", updated_at: "2011-06-23 05:00:00", encrypted_password: "$2a$10$I7Wy8NDMeVcNgOsE3J/ZyubiNAESyxA7Z49H4p1x5xxH...">
And if I check if the password is valid I do the following:
>> user.has_password?("foobar")
=> true
but if I get the user from the database it fails:
user = User.find(1)
user.has_password?("foobar")
=> false
Why does that happen and how can I implement bcrypt to make this work?
Thank you in advance.
My guess would be that since encrypted_password is stored in the database as a string and not a BCrypt::Password, you're not calling into BCrypt's ==, but rather String's ==. You have to instantiate an instance of the Password around the string hash value. That would be where I'd look.
As described over here you have to use the password class of Bcrypt to utilize the ==
def has_password?(submitted_password)
Bcrypt::Password.new(self.encrypted_password) == submitted_password
end