Variable
objects for class and instance variables
Ruby already has Method
objects, why not Variable
objects as well?
Why?
Some methods already exist for interacting with class and instance variables:
Module#class_variable_defined?
Module#class_variable_get
Module#class_variable_set
Object#instance_variable_defined?
Object#instance_variable_get
Object#instance_variable_set
But notice that these all share a common prefix - instance_variable_
or class_variable_
.
This feels a little smelly, let's try to DRY it up with some Variable
objects!
Installation
gem install variables
Requirements
Ruby 1.8.7+
Usage
Let's experiment with a simple User
class.
class User
def initialize(name)
@name = name
end
end
Objects can have any number of instance variables.
user = User.new('Bob') #=> #<User:0x007f8f6a84aa98>
user.instance_variable_get('@name') #=> "Bob"
Similar to Object#method
, the instance_variable
method returns a Variable
object.
name = user.instance_variable(:name) #=> #<InstanceVariable: #<User>@name>
But unlike Object#method
, this method does not require a variable to actually be defined.
undefined = user.instance_variable(:undefined) #=> #<InstanceVariable: #<User>@undefined>
We can check if a variable is defined by using the defined?
method.
name.defined? #=> true
undefined.defined? #=> false
Once we have a Variable
object, we can get
its value.
name.get #=> "Bob"
undefined.get #=> nil
Similar to Hash#fetch
, the fetch
method raises an exception if the variable is undefined.
name.fetch #=> "Bob"
undefined.fetch #=> Variables::UndefinedVariable - undefined variable "undefined"
The fetch
method optionally accepts a default value to return if the variable is undefined.
name.fetch(:default) #=> "Bob"
undefined.fetch(:default) #=> :default
Default values can also be defined with a block
which is yielded with the Variable
name.
name.fetch { |name| "#{name}-default" } #=> "Bob"
undefined.fetch { |name| "#{name}-default" } #=> "@undefined-default"
The Object#instance_variable_fetch
method allows us to fetch
a variable's value by name.
name.fetch #=> "Bob"
user.instance_variable_fetch(:name) #=> "Bob"
We can update a Variable
value by using the set
method.
name.set('Steve') #=> "Steve"
user.instance_variable_get('@name') #=> "Steve"
The replace
method is similar to set
, but it returns the old value instead of the new value.
name.replace('Bob') #=> "Steve"
user.instance_variable_get('@name') #=> "Bob"
We can even temporarily replace
a value for the duration of a block
.
user.instance_variable_get('@name') #=> "Bob"
name.replace('Steve') do
user.instance_variable_get('@name') #=> "Steve"
end
user.instance_variable_get('@name') #=> "Bob"
Note that when using the block
form of replace
, the last expression of the block is returned.
name.replace('Steve') { 1 + 1 } #=> 2
The Object#instance_variable_replace
method allows us to replace
a variable's value by name.
user.instance_variable_get('@name') #=> "Bob"
user.instance_variable_replace(:name, 'Steve') do
user.instance_variable_get('@name') #=> "Steve"
end
user.instance_variable_get('@name') #=> "Bob"
The instance_variable_replace
method also accepts a hash of variables to replace
.
user.instance_variable_get('@name') #=> "Bob"
user.instance_variable_get('@test') #=> nil
user.instance_variable_replace(name: 'Steve', test: 'example') do
user.instance_variable_get('@name') #=> "Steve"
user.instance_variable_get('@test') #=> "example"
end
user.instance_variable_get('@name') #=> "Bob"
user.instance_variable_get('@test') #=> nil
Everything that we can do with instance variables can be done with class variables as well!
example = User.class_variable(:example) #=> #<ClassVariable: User@@name>
example.defined? #=> false
example.set('testing') #=> "testing"
User.class_variable_get('@@example') #=> "testing"
API
Module#class_variable
Module#class_variable_fetch
Module#class_variable_replace
Object#instance_variable
Object#instance_variable_fetch
Object#instance_variable_replace
Variable#defined?
Variable#fetch
Variable#get
Variable#name
Variable#owner
Variable#replace
Variable#set
Testing
bundle exec rspec
Contributing
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don't break it in a future version unintentionally.
- Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
- Send me a pull request. Bonus points for topic branches.
License
MIT - Copyright © 2015 Sean Huber