variables
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