is_a?
and kind_of?
. They take a single argument, a class, and returns true if class is the class of the object, or if class is one of the superclasses of the object or modules included in object. The kind_of?
(or is_a?
) method is generally used for determining behavior based on the type of class of a parameter. Most often I've seen kind_of? appearing in methods that need different behavior depending on the class type of an argument. For example, in a SQL DSL I was recently working on we had a method that would surround string objects with quotes, but would simply call to_s on other objects such as instances of Symbol or Fixnum.
def columns(*args)This code does solve the problem; however, I believe that the code is structured in response to the languages inability to overload methods and behave differently based on type.
args.each |element|
if element.kind_of? String
@column_array << "'#{element}'"
else
@column_array << element.to_s
end
end
An alternative solution is to take advantage of duck typing. If the method were rewritten to execute a method that any class could respond to, the if..else statement becomes unnecessary.
def columns(*args)Clearly, for this example to work each argument must respond to the
args.each |element|
@column_array << element.to_sql
end
end
to_sql
method. Because Ruby's classes can be re-opened this change is trivial.class StringAn added benefit to duck typing is that you can pass any object that responds to
def to_sql
"'#{self}'"
end
end
class Fixnum
def to_sql
self
end
end
class Symbol
def to_sql
self.to_s
end
end
to_sql
. For example, if you wanted a class that would represent null in the database you could define a class that only responds to the to_sql
method.class DBNullFor more information on Duck Typing see the chapter in Programming Ruby.
def to_sql
'null'
end
end
Jay, duck typing examples you gave here a great tip for avoiding the is_a? situations. It also simply looks cleaner and handles all cases you specify with a clear definition of types. - ben @ http://rubyonrailsblog.com
ReplyDeleteVery handy tip. If you're feeling keen and have a few classes you need to add methods to, you can define a helper method to make things extra pretty - this isn't going to format nice :(
ReplyDeletedef add_to_sql_method klass, &block
klass.instance_eval { define_method :to_sql, &block }
end
add_to_sql_method(String) {"'#{self}'"}
add_to_sql_method(Fixnum) {self}
add_to_sql_method(Symbol) {self.to_s}
Good advice on using duck typing instead of class based decisions.
ReplyDeleteBad example in your use case. Perhaps the most common security problem with web apps is SQL Injection attacks and your String#to_sql method is vulnerable as it fails to escape quotes.
Using parameterized sql binding is preferable, but if you must concat strings into sql statements at least do this:
class String; def to_sql
"'#{self.gsub(/\\/,'\&\&').gsub(/'/,"''")}'"
end; end
@dbenhur: Thanks for the code sample. I completely agree that if your app is public facing you have to look out for situations like you describe.
ReplyDeleteI haven't had to deal with these types of issues recently since I've been working on internal only apps.
Thanks again for the feedback.
Frankly, even internal applications should not be written in a lazy, insecure way.
ReplyDelete