Topic: Refactor Field Checks

This is becoming a familiar design pattern for me:

validates_length_of       :login, :within => 3..40, :if => :login_is_filled?
validates_length_of       :email, :within => 3..100, :if => :email_is_filled?
validates_presence_of     :password, :if=>:password_required?
validates_presence_of     :password_confirmation, :if => :password_required? && :password_is_filled?

def password_is_filled?
!password.blank?
end

def email_is_filled?
!email.blank?
end

def login_is_filled?
!login.blank?
end


Sometimes those is_filled? methods can get really long and repetitive, so I try to be clever.

validates_length_of :login, :within => 3..40, :if => is_filled?('login')

def is_filled?(att)
    !att.blank?
end


Of course that fails because

undefined method `is_filled?' for User:Class

Another problem I notice is that the blank? method wont work on something that is not a model attribute (such as password confirmation). Is there any way to get around this?



BTW: This wold be a perfect first post for the new refactoring forum (hint, hint)

Last edited by pimpmaster (2007-08-11 07:06:57)

Re: Refactor Field Checks

is_filled? is not a class method.

User.is_filled?('login')
# NoMethodError: undefined method `is_filled?' for User:Class

User.find(2).is_filled?('login')
# true


The method will always return true because it is working with a string. !"login".blank?
EDIT: except if you pass in an empty string etc. but anyways...
You could try working around it:
def is_filled?(att)
  attr = attr.to_s
  return false if !respond_to? att
  !self.send(att).blank?
end

But why not try some meta programming? I've done very little of it before so I'm sure this needs tidying up, but this works and should get you started:
def self.build_presence_methods_for(*attrs)
  attrs.each do |attr|
    eval "def #{attr}_is_filled?; !#{attr}.blank?; end"
  end
end

build_presence_methods_for :password, :email, :login

Last edited by vwoo (2007-08-11 13:23:47)

Vincent Woo Ruby on Rails Blog

Re: Refactor Field Checks

This freaking rocks!

I think it may even be worthy of submitting a patch

just one question... what exactly does eval do? I always associate it with wicked magic you are not supposed to talk about

Last edited by pimpmaster (2007-08-11 13:55:20)

Re: Refactor Field Checks

Ruby API Doc wrote:

Evaluates the Ruby expression(s) in string. If binding is given, the evaluation is performed in its context. The binding may be a Binding object or a Proc object. If the optional filename and lineno parameters are present, they will be used when reporting syntax errors.

http://www.rubybrain.com/api/ruby-1.8.6 … ;name=eval

Last edited by shadow (2007-08-11 13:59:24)

Re: Refactor Field Checks

Thats all well and good, but Im still lost with the additional jargon (binding and lineno parameters...wha??)

Re: Refactor Field Checks

eval is frowned upon because, if it involves user input, it could be executing malicious code. I think the ruby community is more open to its usage though.

With binding, you can pass around the variable scope context. If the example at the eval documentation doesn't make sense, maybe the one at http://ruby-doc.org/core/classes/Binding.html will.

I think you can use class_eval() instead of eval here so that binding is no longer an option smile

http://ruby-doc.org/core/classes/Module.html#M001673
The documentation for class_eval() explains the filename and lineno params better:

# The optional filename and lineno parameters set the text for error messages.

   class Thing
   end
   a = %q{def hello() "Hello there!" end}
   Thing.module_eval(a)
   puts Thing.new.hello()
   Thing.module_eval("invalid code", "dummy", 123)

# produces:

   Hello there!
   dummy:123:in `module_eval': undefined local variable
       or method `code' for Thing:Class

Vincent Woo Ruby on Rails Blog

Re: Refactor Field Checks

That is a bit more enlightening.

The funny thing about metaprogramming is that questions just lead to other questions.

I have used %w[arg1 arg2] before with great success, but I've never seen %q before... I wish google didn't ignore percent symbols.

Also, in the above example "invalid code" is specified as an argument but doesnt show up in the
result? What's up with that?

Overall I think the major hurdle I have with fluency is the abstract way that classes and functions are explained. I'm a designer by trade and my brain is not so quick to register the purpose of arbitrary code. I'm always left thinking "That's cool I suppose...but what can this code actually DO in the real world?"

The funny thing is that I learned more about eval from the first code snippet you posted, than I have from all the books I've read. Applied knowledge really is the key

Re: Refactor Field Checks

Just another way to create strings.

%q single quote
%Q double quote

You can use any punctuation type character to begin and end the string.

%Q{  <img src="#{url}" alt="test" />  }
%Q!  This is a code block: @users.each {|user| p user.age}  !

The invalid code example is doing this:
class Thing
  def hello
    "Hello there!"
  end

  invalid code
end


The last line in the class won't work.

Vincent Woo Ruby on Rails Blog

Re: Refactor Field Checks

Wow, the rabbit hole seems to go deeper the further I progress.

Thanks for the clear explanations, vwoo.. I actually get it now