Ruby Flashcards
Singleton
Ensures that only one instance of Class can be created.
What is the difference between a block, lambda, and proc
lambda and procs are objects, block is not.
lambda’s return does not effect the output when called. procs can.
Metaprogramming: Class Instance Variables
Inside a method, we can set an instance variable of the current object. This is what we do inside the initialize instance method.
When an instance variable is stored on a class, it is sometimes called a class instance variable. Don’t let the name wow you though; we’re just using a typical instance variable. This is similar to how class methods are merely methods that are called on a Class object.
However, class instance variables don’t interact very nicely with inheritance.
Class variables (not class instance variables) are shared between super-class and subclass. Let’s see this:
class Dog def self.all @@dogs ||= [] end
def initialize(name) @name = name
self.class.all << self end end
class Husky < Dog end
h = Husky.new(“Rex”)
Dog.all # => #
Why should we avoid global variables?
The reason is that since global variables live outside any class, they aren’t very object oriented. Data is normally stored in one of two places:
Inside an object (instance, class instance, and class variables) Inside a local variable; the local variable lives as long as the current method call. If you need to access an object inside a method, it is typical to pass the object into the method. If you need to return a result from a method, it is typical to use return to pass it back. There is seldom a reason to store things globally.
There are occasionally exceptions: sometimes an object will be useful throughout your entire program, in which case you may want to make it globally accessible. One classic example is the $stdin and $stdout variables, which contain File objects (technically, IO objects, but they’re very similar) that you can use to read/write to the user.
Here’s how puts and gets are defined:
def puts(*args) $stdout.puts(*args) end
def gets(args)
$stdin.gets(args)
end
This eliminates most of the need to use these variables explicitly. However, say you wanted to write your output differently depending on whether the user was reading your output in a terminal or dumping your output to a file. In Bash, they can specify this by either:
What is Reflection?
The ability for a program to examine itself.
EX:
obj = Object.new
obj.methods
=> [:nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, …]
Object#methods returns an array of symbols, each the name of a method that can be sent to the object. This is helpful for debugging, but not super useful in production code.
Metaprogramming: What is send?
“send” sends a message to an object instance and its ancestors in class hierarchy until some method reacts (because its name matches the first argument).
Practically speaking, those lines are equivalent:
1.send ‘+’, 2
1.+(2)
1 + 2
Note that send bypasses visibility checks, so that you can call private methods, too (useful for unit testing).
If there is really no variable before send, that means that the global Object is used:
send :to_s # “main”
send :class # Object
When is something like send useful? Why not just call the method the normal way? Well, using send lets us write methods like this:
def do_three_times(object, method_name) 3.times { object.send(method_name) } end
class Dog def bark puts "Woof" end end
dog = Dog.new do_three_times(dog, :bark)
Metaprogramming: What is define_method?
Defines an instance method in the receiver. The method parameter can be a Proc, a Method or an UnboundMethod object. If a block is specified, it is used as the method body. This block is evaluated using instance_eval, a point that is tricky to demonstrate because define_method is private. (This is why we resort to the send hack in this example.)
class Dog # defines a class method that will define more methods; this is # called a **macro**.
def self.makes_sound(name) define_method(name) { puts "#{name}!" } end
makes_sound(:woof)
makes_sound(:bark)
makes_sound(:grr)
end
dog = Dog.new
dog. woof
dog. bark
dog. grr
common examples:
We don’t write macros every single day, but they are frequently quite useful. Some of the most famous macro methods are:
attr_accessor: defines getter/setter methods given an instance variable name.
belongs_to/has_many: defines a method to perform a SQL query to fetch associated objects.
Metaprogramming: What is method_missing?
When a method is called on an object, Ruby first looks for an existing method with that name. If no such method exists, then it calls the Object#method_missing method. It passes the method name (as a symbol) and any arguments to #method_missing.
The default version simply raises an exception about the missing method, but you may override #method_missing for your own purposes:
class T def method_missing(*args) p args end end T.new.adfasdfa(:a, :b, :c) # => [:adfasdfa, :a, :b, :c] Here's a simple example:
class Cat
def say(anything)
puts anything
end
def method_missing(method_name) method_name = method_name.to_s if method_name.start_with?("say_") text = method_name[("say_".length)..-1]
say(text) else # do the usual thing when a method is missing (i.e., raise an # error) super end end end
earl = Cat.new
earl.say_hello # puts “hello”
earl.say_goodbye # puts “goodbye”
Using method_missing, we are able to “define” an infinite number of methods; we allow the user to call any method prefixed say_ on a Cat. This is very powerful; it isn’t possible to do this using define_method itself.
However, overriding method_missing can result in difficult to understand/debug to code, and should not be your first resort when attempting metaprogramming. Only if you want this infinite expressability should you use method_missing; prefer a macro if the user just wants to define a small set of methods.
Metaprogramming: What are dynamic finders?
An Advanced Example: Dynamic Finders
What if we overrode #method_missing in ActiveRecord::Base to work for #find_by_* methods, so we could do the following:
User.find_by_first_name_and_last_name(“Ned”, “Ruggeri”)
User.find_by_username_and_state(“ruggeri”, “California”)
We could do this by parsing the “missing” method name and combining the column names (separated by ands) with the given arguments. It might look something like this:
class ActiveRecord::Base def method_missing(method_name, *args) method_name = method_name.to_s if method_name.start_with?("find_by_") # attributes_string is, e.g., "first_name_and_last_name" attributes_string = method_name[("find_by_".length)..-1]
# attribute_names is, e.g., ["first_name", "last_name"] attribute_names = attributes_string.split("_and_")
unless attribute_names.length == args.length raise "unexpected # of arguments" end search_conditions = {} attribute_names.length.times do |i| search_conditions[attribute_names[i]] = args[i] end # Imagine search takes a hash of search conditions and finds # objects with the given properties. self.search(search_conditions) else # complain about the missing method super end end end NB: Dynamic finders were actually a feature of Rails until just recently. Rails 4.2 deprecated (supported, but didn't recommend) dynamic finders, and as of Rails 5, they are no longer supported. Although, they are quite handy, they tend to lead to overly verbose code and are not very performant. For these reasons we also recommend against using them. Check out this blog post if you'd like to learn more.
Classes
Classes are types of Modules (not important), which are Objects. In Ruby everything is an Object, even Classes!
To summarize: Object is of type Class, which is a subclass of Object itself. Whoa!
How would we create a hand rolled attr_accessor?
class AttrAccessorObject def self.my_attr_accessor(*names) names.each do |name| define_method(name) do instance_variable_get("@#{name}") end
define_method("#{name}=") do |value| instance_variable_set("@#{name}", value) end end end end