Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

Part 4. Ruby metaprogramming Specs: spec/attr_accessor_with_history_spec.rb Skeleton: lib/attr_accessor_with_history.rb In lecture we saw how attr_accessor uses metaprogramming to create getters and setters for object attributes on

Part 4. Ruby metaprogramming

Specs: spec/attr_accessor_with_history_spec.rb

Skeleton: lib/attr_accessor_with_history.rb

In lecture we saw how attr_accessor uses metaprogramming to create getters and setters for object attributes on the fly.

Define a method attr_accessor_with_history that provides the same functionality as attr accessor but also tracks every value the attribute has ever had:

class Foo attr_accessor_with_history :bar end f = Foo.new # => # f.bar = 3 # => 3 f.bar = :wowzo # => :wowzo f.bar = 'boo!' # => 'boo!' f.bar_history # => [nil, 3, :wowzo] 

(Calling bar_history before bar's setter is ever called should return nil.)

History of instance variables should be maintained separately for each object instance. that is:

f = Foo.new f.bar = 1 ; f.bar = 2 g = Foo.new g.bar = 3 ; g.bar = 4 g.bar_history 

then the last line should just return [nil,3], rather than [nil,1,2,3].

If you're interested in how the template works, the first thing to notice is that if we define attr_accessor_with_history in class Class, we can use it as in the snippet above. This is because a Ruby class like Foo or String is actually just an object of class Class. (If that makes your brain hurt, just don't worry about it for now. It'll come.)

The second thing to notice is that Ruby provides a method class_eval that takes a string and evaluates it in the context of the current class, that is, the class from which you're calling attr_accessor_with_history. This string will need to contain a method definition that implements a setter-with-history for the desired attribute attr_name.

HINTS:

Don't forget that the very first time the attribute receives a value, its history array will have to be initialized.

An attribute's initial value is always nil by default, so if foo_history is referenced before foo has ever been assigned, the correct answer is nil, but after the first assignment to foo, the correct value for foo_history would be [nil].

Don't forget that instance variables are referred to as @bar within getters and setters, as Section 3.4 of ELLS explains.

Although the existing attr_accessor can handle multiple arguments (e.g. attr_accessor :foo, :bar), your version just needs to handle a single argument.

Your implementation should be general enough to work in the context of any class and for attributes of any (legal) variable name.

Note that one powerful metaprogramming feature in Ruby is class_eval that can be called in the meta-class Class.class_eval can interpret a string on the fly to create some new code. In the example below, we define add_method() in the meta-class (and, through inheritance, available to any class). When called, this method defines a new method that returns 42 (notice how #{name} gets replaced with the parameter passed to add_method).

 class Class def add_method (name) class_eval %Q{ def #{name}() 42 end } end end class MyClass add_method :my_method end mc = MyClass.new puts mc.my_method # => 42 

Part 5: Cartesian Product

Skeleton: lib/cartesian_product.rb

Given two collections (of possibly different lengths), we want to get the Cartesian product of the sequencesin other words, every possible pair of N elements where one element is drawn from each collection.

For example, the Cartesian product of the sequences a==[:a,:b,:c] and b==[4,5] is: ab == [[:a,4],[:a,5],[:b,4],[:b,5],[:c,4],[:c,5]]

Create a method that accepts two sequences and returns an iterator that will yield the elements of the Cartesian product, one at a time, as a two-element array.

It doesn't matter what order the elements are returned in. So for the above example, the ordering [[:a,4], [:b,4], [:c,4], [:a,5], [:b,5], [:c,5]] would be correct, as would any other ordering.

It does matter that within each pair, the order of the elements matches the order in which the original sequences were provided. That is, [:a,4] is a member of the Cartesian product ab, but [4,:a] is not. (Although [4,:a] is a member of the Cartesian product ba.)

Here's some sample code showing how your iterator could be used:

class CartesianProduct include Enumerable # your code here end c = CartesianProduct.new([:a,:b], [4,5]) c.each { |elt| puts elt.inspect } # [:a, 4] # [:a, 5] # [:b, 4] # [:b, 5] c = CartesianProduct.new([:a,:b], []) c.each { |elt| puts elt.inspect } # (nothing printed since Cartesian product # of anything with an empty collection is empty)

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

Programming The Perl DBI Database Programming With Perl

Authors: Tim Bunce, Alligator Descartes

1st Edition

1565926994, 978-1565926998

More Books

Students also viewed these Databases questions

Question

T F Most countries have similar economic systems.

Answered: 1 week ago