classes in run time • Make your application more DRY, lightweight, intuitive, extendable, and more maintainable • Code that writes code, make your code more concise, reduce duplicate code • Code that treats code as data
@engine_info end def engine_price=(price) @engine_price = price end def engine_price @engine_price end def wheel_info=(info) @wheel_info = info end def wheel_info @wheel_info end def wheel_price=(price) @wheel_price = price end def wheel_price @wheel_price end def airbag_info=(info) @airbag_info = info end def airbag_info @airbag_info end def airbag_price=(price) @airbag_price = price end def airbag_price @airbag_price end def alarm_info=(info) @alarm_info = info end def alarm_info @alarm_info end def alarm_price=(price) @alarm_price = price end def alarm_price @alarm_price end def stereo_info=(info) @stereo_info = info end def stereo_info @stereo_info end def stereo_price=(price) @stereo_price = price end def stereo_price @stereo_price end end
do |feature| define_method("#{feature}_info=") do |info| instance_variable_set("@#{feature}_info", info) end define_method("#{feature}_info") do instance_variable_get("@#{feature}_info") end define_method "#{feature}_price=" do |price| instance_variable_set("@#{feature}_price", price) end define_method("#{feature}_price") do instance_variable_get("@#{feature}_price") end end end
:alarm_price, :stereo_info, :stereo_price end class CarModel def self.features(*args) args.each do |feature| attr_accessor "#{feature}_price", "#{feature}_info" end end features :engine, :wheel, :airbag, :alarm, :stereo end
attr_accessor :magic_points def cast(spell) “Your cast #{spell}” end end class Wizard < Character include Magic attr_accessor :name def initialize(options = {}) @name = options.fetch :name, ‘NPC’ @hit_points = rand(1..4) end end
= {}) @name = options.fetch :name, 'NPC' @hit_points = rand(1..4) end end gandalf # => #<Wizard:0x007fbea4b2f3d0 @name="Gandalf", @hit_points=1> < Object < Basic Object First, look at the instance Then, look in the class’s methods Next, find in included module Then, go to the superclass Continue until BasicObject Last in, first out
self.singleton_class.instance_methods - self.class.instance_methods singleton_methods.include? method end end foo = "A string" def foo.shout puts foo.upcase end # shout is a singleton method. p foo.singleton_method?(:shout) # ⇒ true
of Class itself, not its instances. • Class methods are just singleton methods of a Class class Item def self.show puts “Class method show invoked” end end Item.show # => “Class method show invoked”
arguments: #{args.join(', ')}" end end m = MyGhostClass.new m.awesome_method("one", "two") # => awesome_method was called with arguments: one, two m.another_method("three", "four") # => another_method was called with arguments: three, four :method_missing
if @subject.respond_to?(method) @subject.send(method) else super(method) end end def respond_to?(method, include_private = false) super || @subject.respond_to?(method, include_private) end end proxy = Proxy.new(Time) proxy.respond_to?(:now) # => true proxy.now # => Fri Feb 05 00:34:53 -0500 2010
contents @tweets end private def direct_messages end end tweets = ['Compiling!', 'Bundling...'] timeline = Timeline.new(tweets) timeline.contents timeline.send(:contents) timeline.send(“contents”) timeline.send(:direct_messages) send can run private or protected methods
contents @tweets end private def direct_messages end end tweets = ['Compiling!', 'Bundling...'] timeline = Timeline.new(tweets) timeline.send(:direct_messages) send can run private or protected methods timeline.public_send(:direct_messages) private method `direct_messages' called for #<Timeline: 0x007fd273904eb0> (NoMethodError) prevents running private or protected methods
• Block syntax uses either do..end or curly braces ({}) [1, 2, 3].each do |num| puts num end # => Prints 1, 2, 3 on separate lines [1, 2, 3].each { |num| puts num } # => Prints 1, 2, 3 on separate lines
others don't? def block_test puts "We're in the method!" puts "Yielding to the block..." yield puts "We're back in the method!" end block_test { puts ">>> We're in the block!" } We're in the method! Yielding to the block... >>> We're in the block! We're back in the method! • It's because methods that accept blocks have a way of transferring control from the calling method to the block and back again.
posted @status = :posted end def deleted @status = :deleted end end class Tweet states = [:draft, :posted, :deleted] states.each do |status| define_method status do @status = status end end end
end def tweets @tweets end def contents @tweets end end class Timeline def initialize(tweets = []) @tweets = tweets end def tweets @tweets end alias_method :contents, :tweets end
opening it in a text editor and adding new state or behaviour to it. • Excutes block in context of the class class Tweet attr_accessor :status, :created_at def initialize(status) @status = status @created_at = Time.now end end Tweet.class_eval do attr_accessor :user end tweet = Tweet.new("Learning class_eval") tweet.user = "tom"
old = Monk.new Monk.class_eval("def initialize; @zen = 42; end") Monk.class_eval("def zen; @zen; end") less_old = Monk.new puts "from the old instance: #{old.zen}" # => from the old instance: puts "from the new instance: #{less_old.zen}" # => from the new instance: 42 old.zen = 43 puts "from the old instance, with @zen set: #{old.zen}" # => from the old instance, with @zen set: 43 Code as data
attr_accessor :user, :status end tweet = Tweet.new tweet.instance_eval do self.status = “Changing the tweet’s status” end Inside the block, self is the Tweet instance
if block_given? end end Tweet.new do |tweet| tweet.status = "Learning instance_eval" tweet.user = "Tom" end class Tweet attr_accessor :user, :status def initialize(&block) instance_eval(&block) if block_given? end end Tweet.new do self.status = "Learning instance_eval" self.user = "Tom" end
ClassMethods def fetch_from_twitter(user) end end end class Image include ImageUtils extend ImageUtils::ClassMethods end image = user.image image.preview Image.fetch_from_twitter(‘tom’)
def transfer(destination) end module ClassMethods def fetch_from_twitter(user) end end end class Image include ImageUtils extend ImageUtils::ClassMethods end image = user.image image.preview Image.fetch_from_twitter(‘tom’) self.included is called by Ruby when a module is included in a class
def transfer(destination) end module ClassMethods def fetch_from_twitter(user) end end end class Image include ImageUtils end image = user.image image.preview Image.fetch_from_twitter(‘tom’) base is Image Class