I have the need to use a Stack-like data structure for a program that I am writing and I know that Ruby doesn't have an explicit Stack data-structure, but that the Array class has all of the properties that make a Stack: push
, pop
, size
, clear
, isEmpty
, inspect
, to_s
.
In searching online I found various posts using this syntax to extract features of the Array class into a subclass:
Stack = Array.extract([
:push,
:pop,
:size,
:clear,
:inspect,
:to_s
])
s = Stack.new
s.push 1
s.push 2
s.push 3
s # => [1, 2, 3]
s.pop # => 3
s # => [1, 2]
I would like to do something similar to this so my subclass of Array is restricted in what calls it can make, but it appears that the extract method is no longer in the Array class API.
Questions:
- This feature was removed for a reason, what is the detriment of something like this?
- How can achieve functionality similar to this using Ruby 1.9.3? Right now I am just delegating the calls that I need to the Array class, but all the other methods in the Array class can still be called on my Stack object, which I don't want to allow.
Take this code, for example:
class Stack
def initialize
@array = []
end
def push val
@array.push val
end
def pop
@array.pop
end
end
Here you have private instance var to which you delegate selected methods. Other methods cannot be called directly. The syntax can be sweetened and made more "rubesque" with some metaprogramming, but basic idea is as shown above.
Of course, one can always get to that private var via instance_variable_get
and there's nothing you can do about it. This is Ruby!
Make clean safe public interface. And if someone tries to meddle with inner parts and breaks something, it's his problem.
Update
If you're using ActiveSupport (which comes with Rails), then there's a simpler way of doing this.
# load ActiveSupport if not in Rails env
require 'active_support/core_ext'
class Stack
def initialize
@impl = []
end
# the "extract" part :)
delegate :count, :push, :pop, to: :@impl
end
s = Stack.new
s.push(3).push(4)
s.count # => 2
Or look at similar answer by @AndrewGrimm.
If you're going to be doing a lot of delegating, then you may want to use the delegation methods Ruby provides.
require "forwardable"
class Stack
extend Forwardable
def_delegators :@array, :push, :pop
def initialize
@array = []
end
end
Not only does it mean less typing, but if you're familiar with Forwardable
, it's easier to read. You know that your class is merely delegating, without having to read the code for Stack#push
or Stack#pop
.
Maybe you can undefine methods that you don't like, although it looks wired.
class Stack < Array
DISLIKE_IMS = instance_methods - Object.instance_methods - [:public, :pop, :size, :to_s, :inspect, :clear]
DISLIKE_CMS = methods - Object.methods
DISLIKE_IMS.each do |im|
class_eval "undef #{im}"
end
DISLIKE_CMS.each do |cm|
instance_eval "undef #{cm}"
end
end