January 16, 2018
We can always extend classes in Ruby. Both predefined and classes we create ourself. We say that classes are always open.
If you want to some concrete examples look at this gist with some code.
Before you extend a built-in class be sure that it should be there. A string should not have methods to generate urls to a placeholder image, even if it could. It does not belong in the String class.
Most of the time you should not add methods to classes. The placeholder image url example above should not be on the String class. It might fit on some other class, maybe your ImageUrl
class, or not at all.
def placeholder_image_url
"http://via.placeholder.com/100x100"
end
Or in Module if you want it name spaced.
module Placeholders
def Placeholders.image_url size = "200"
"http://via.placeholder.com/#{size}x#{size}"
end
end
An option could be to create a subclass of a known class. For example we could create a class that can count vowels.
class VowelString < String
def vowels
chars.select{ |char| "aoueiy".include? char }.count
end
end
This is just classic inheritance.
We can use it like any other class.
sample_vowel_string = VowelString.new "My super cool string"
puts "My sample string has #{sample_vowel_string.vowels} vowels"
In Ruby we have single inheritance, so we can not subclass from both String
and Integer
.
Add a method to Integers that returns a random value between 0 and the integer value.
begin
19.random # Undefined method 'random' for 19:Integer
rescue NoMethodError
puts "19.random is not defined"
end
puts "extending Integer"
class Integer
def random
Random.rand(self)
end
end
puts "19.random => #{19.random}"
self
refers to the instance of the object itself. In the case of our Integer self is 19.
To add a class method is also simple.
begin
Integer.random 3
rescue NoMethodError
puts "Integer.random is not defined"
end
class Integer
def self.random number
Random.rand number
end
end
puts "Integer.random 3 => #{Integer.random 3}"
We add methods to self.
We can add behaviour to classes with mixins. We include
a module in the class.
module Vowels
ALL = "aoueiy"
def vowels
self.chars.select{ |char| ALL.include? char }
end
def count_vowels
vowels.count
end
end
Now we can access constants just like normal modules puts "All vowels: #{Vowels::ALL}"
.
We can then make String vowel aware.
class String
include Vowels
end
and use it like any method on String
.
"Mu super cool string".count_vowels # 6
We can include many modules in our class, and thus achieving a sort of multi inheritance.
We use Kaminari to paginate our application. We need to render the meta data in the json response, and to do that we extend ActiveRecord to provide this functionalliy.
Extend ActiveRecord::Relation with a pagination_info
method.
In lib/active_record_relation_extension.rb
:
module ActiveRecordRelationExtension
def pagination_info
if respond_to? :total_count
{
total_count: total_count,
current_page: current_page,
next_page: next_page,
prev_page: prev_page,
}
else
nil
end
end
end
Then we also need to configure it. We push it to the inheritance stack of Relation.
config/initializers/active_record_relation_pagination.rb
ActiveRecord::Relation.send(:include, ActiveRecordRelationExtension)
Written by Simon Ström as a way to remember. It's a dev log of thinks I want to remember.