Archive for the ‘Ruby’ Category
StrongRuby : gradual typing for Duby.
Abstract :
Static and dynamic type systems have well-known strengths and weaknesses. Statically typed programming languages incorporate a built-in static type system. Every legal program must successfully typecheck according to the rules of the type system.
Gradual typing provides the benefits of both static and dynamic checking in a single language by allowing the programmer to control whether a portion of the program is type checked at compile-time or run-time by adding or removing type annotations on variables. , that have no effect on the dynamic semantics of the language.
The complete proposal here : strongruby.pdf
Ruby idioms : Avoid check class membership
Avoid use class, is_a? and kind_of? to check class membership. Replace these statement with a message to the object that respond to the message. The code below is an anti-pattern.
if mortgage.class==FixRateMortgage mortgage.change_rate_not_allowed else mortgage.rate=rate end
The code below is more idiomatic in Ruby.
class Mortgage attr_accessor :rate end class FixedRateMortgage < Mortgage def initialize() @rate=5 end def rate=(rate) change_rate_not_allowed end def change_rate_not_allowed puts "The rate in a fixed rate mortage can not be change" end end
Sending the method rate= to either kind of classes will result in the appropriate behaviour.
Commons bugs in ruby: blocks and local variables
Originally blocks did not have truly local variables. The block
parameters were really local variables in the enclosing method.
x=0 1.upto 100 do |z| x = x+z end x #=> 5050
Commons bugs in ruby: Changing collection while iterating over it
You should never, never, never iterate over a collection which the iteration loop somehow modifies. Elements of the collection will be moved during the iteration. Elements might be missed or handled twice.
b=(1..10).collect # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] b.each do |elem| b.delete elem end #=> [2, 4, 6, 8, 10]
Ruby idioms : Add a method to a concrete instance.
Sometimes can be useful to add a method in a specific instance of a class. We can do that with the next code :
class Person end pedro = Person.new peter = Person.new # inject a method in the instances def pedro.hello_world; puts "Hola Mundo"; end def peter.hello_world; puts "Hello World"; end pedro.hello_world #=> Hola Mundo peter.hello_world #=> Hello World
A more useful example, an Array that represent a list of employees that must be sortable by any of the employee attributes : (via Neal Ford)
employees = [] def employees.sort_by_attribute(sym) sort {|x, y| x.send(sym) <=> y.send(sym)} end
Dynamics finders in ActiveRecord using AOP.
Yesterday, I have been playing with Aquarium, an AOP framework for ruby. I decided hack a little bit. I try to refactor the dynamics finders of ActiveRecord, using an AOP approach.
First lets try to understand what are and how dynamics finders works.
This are queries are equvalent :
User.find(:first, :conditions => ["name = ?", name]) User.find_by_name(name) User.find(:all, :conditions => ["city = ?", city]) User.find_all_by_city(city) User.find(:all, :conditions => ["street = ? AND city IN (?)", street, cities]) User.find_all_by_street_and_city(street, cities)
And how this cool methods are generated? They are not generated, ActiveRecords use a bit of the method_missing magic to parse the method_name to find a pattern. A simplify version of the method_missing :
def method_missing(method_id, *arguments) if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s) # find... elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s) # find_or_create... else super end end
Now its time to introduce the AOP in the recipe. We create two advices, the pointcut is the method missing. This two advices perform as a chain, first the last defined.
class Base before :method=>:method_missing do |this, execution_point, *args| if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(args.fir st.to_s) # find_by_xxx... #do something smart and break the flow end end before :method=>:method_missing do |this, execution_point, *args| if match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/. match(args.first.to_s) # find_or_xxx #do something smart and break the flow end end # And so on, until you have a minimum common behaviour or an empty method_missing. end
We can extract more behaviour from the method missing, We can repeat this process until we have the minimum common behaviour or a empty method_missing. Sometimes we neer to break the chain at some point. In this cases we can uses around.
One of the benefits of this approach is that will be more easy to introduce new finder in rails. Currently to create a new finder we need to create a plugin in the vendor folder. For example we are going to create a find_like finder. We write our code in the vendor/plugins/find_like/lib/find_like.rb :
module ActiveRecord class Base class << self private alias_method :previous_method_missing, :method_missing def method_missing(method_id, *arguments) if match = /find_(all_like|like)_([_a-zA-Z]\wo*)/.match(method_id.to_s) # do something smart else previous_method_missing(method_id, *arguments) end end end end end
If we use the AOP approach we need to write, also our code in the vendor/plugins/find_like/lib :
module FinderLike # A custom finder finder_like_xxx before :types => :Base, :methods =>:method_missing do |execution_point, *args| if match = /find_(all_like|like)_([_a-zA-Z]\w*)/.match(args.first.to_s) # Do something smart and break the flow end end end
I Don’t know if this is a good design in this particular case. But at least was a funny Kata.
Default values and Hash parameters
I had talked before about it. We can use a hash to pass parameter to our method, and que can merge with another hash that contain the defaults values. Why to do that :
- Set Default Values in Methods : Avoid unexpected behaviour.
- Simple Hash as Default Method Parameter : This form allows you to add functionality to methods without breaking existing calls
def add_book(p_arg) p_arg = {:title=>"no title",
:author=>"anonymous",
:language=>"esperanto"}.merge! p_arg end
add_book :title=>"El Qujote",
:author=>"Miguel De Cervantes",
:language=>"spanish" => {:language=>"spanish", :title=>"El Qujote", :author=>"Miguel De Cervantes"}
add_book :title=>"El Qujote",
:author=>"Miguel De Cervantes" => {:language=>"esperanto", :title=>"El Qujote", :author=>"Miguel De Cervantes"}
add_book :title=>"El Qujote",
:author=>"Miguel De Cervantes"
:foo=>:bar => {:language=>"esperanto", :foo=>"bar" ,:title=>"El Qujote", :author=>"Miguel De Cervantes"}
Ramdon tips for Rails
- MVC as a constraint
- Over do MVC
- Don’t put any code in the view
- Don’t do any DB operation in the controller
- Set Default Values in Methods : Avoid unexpected behaviour.
- Simple Hash as Default Method Parameter : This form allows you to add functionality to methods without breaking existing calls
- Helpers are hard to test, its will turn Rails into PHP
Multiples assignmets in a for loop clause.
1 2 3 4 5 |
for hello, world in [ [:Hola, :Mundo], [:Hallo, :Welt], [:Foo, :Bar] ] puts "#{hello} #{world}" end |
Comments (3)