I want to create an instance variable when a certain method is called and also set it to a default value. The instance should then only be able to get the variable but not set it. Here's a piece of code to showcase my problem:
class Foo
def self.bar(var)
attr_reader name.to_sym
instance_variable_set("##{var.to_s}",[var.to_s])
end
end
class Bar < Foo
bar :foo
end
puts Bar.new.foo
When I run this code I expect to get ["foo"] but instead I get nil. It seems to be a problem of scope but after fiddling around with the code for some time I just don't seem to get it right.
Edit:
I just found an exceptionally good article (read till the end) which addresses this problem very concisely. Click Me
This is I think the sort of pattern you are trying to create:
class Foo
def self.all_properties
#all_properties ||= []
end
def self.property( pr_name )
attr_reader pr_name.to_sym
instance_variable_set( "#default_#{pr_name.to_s}", [pr_name.to_s] )
all_properties << pr_name.to_sym
end
def set_default_properties
self.class.all_properties.each do | pr_name |
default = self.class.instance_variable_get( "#default_#{pr_name.to_s}" )
instance_variable_set( "##{pr_name}", default.clone )
end
end
def initialize
set_default_properties
end
end
class Bar < Foo
property :foo
end
p Bar.new.foo
It is more complex than you might initially assume. You have to put the list of managed properties, and their default values somewhere. I have done this above with the class instance variables #all_properties and #default_foo. You can get them forwarded into each instance with a little more meta-programming than you have in the question - basically a copy from defaults stashed in the class when it was defined has to be made during instantiation. Why? Because the class definition is not re-run during instantiation, it happens just once beforehand (unless you start modifying the class on-the-fly in the constructor - but that would be unusual!)
Note that the clone in the code above is not quite enough to prevent instances interfering with each other. I haven't implemented a deep clone (left as exercise to anyone reading the code), but the following variation of that line will work for the code as it stands, because the structure of default data is always the same:
instance_variable_set( "##{pr_name}", [ default[0].clone ] )
Related
Sorry that I haven't made the title very clear but I honestly can't work out how to phrase my question any better; if some one can think of a better title, then please by all means change it.
Take a look at the following example:
module Foo
def method(value)
Bar.instance_exec(self.method(value))
end
class Bar
def self.method(value)
print value
end
end
end
Is that the only way to make is so Bar's method is available within the namespace of Foo without the need to call Bar or is there a more elegant way to do this? Perhaps method_missing might work but again, a bit cumbersome.
EDIT:
Further example
Module Foo
class SomeClassController < ApplicationController
def index
method(#user.id) #instead of Bar.method(#user.id)
end
end
I'm building an engine but I don't want to have to refer to Engine every time I want to use a method from it that should be available everywhere within that Main Name Space (Foo in this instance) AS there more like utility methods BUT they need to be done within the scope of Engine.
EDIT:
Turns out I wasn't using the code correctly
module Foo
def method(value)
return Bar.instance_exec(value) { |v| return self.method(value)}
end
class Bar
def self.method(value)
return (value + 59)
end
end
end
will the method inside of the block of instance_exec return outside of it so that def method within module Foo will return the correct value.
EDIT: I realise it is kind of vague but trust me when I say that instance_exec is exactly what I was searching for, that and instance_exec does in deed return outside of it's execution block.
The answer to my problem is as follows using a rails engine as the example to add a more understandable context
engine.rb:
module Foo
class Engine
class SomeClass
def method(value)
return (value + 59)
end
end
end
end
otherActions.rb
module Foo
def method(value)
return Engine.instance_exec(value){ |v| return self::SomeClass.method(v) }
end
end
Engine_Controller.rb
Module Foo
class EngineController < ApplicationController.rb
def index
#user.some_field = method(params[:user_input])
end
end
end
I think that should work well, I know everything Up to the usage of method within def index at least, that I'm not sure about but the main point of the question has been answered within otherActions.rb
I'm testing a class level instance variable (and setters) in a gem using RSpec. I need to test the following:
The correct default value is provided if the setter is never used.
The variable can be successfully updated through the setters.
Obviously there is a run order issue here. If I change the values using the setters, I lose memory of what the default value was. I can save it to a variable before the setter test and then reset the value at the end, but that only protects me if all setter tests follow the same practice.
What is the best way to test the default value of the variable?
Here is a simple example:
class Foo
class << self
attr_accessor :items
end
#items = %w(foo bar baz) # Set the default
...
end
describe Foo do
it "should have a default" do
Foo.items.should eq(%w(foo bar baz))
end
it "should allow items to be added" do
Foo.items << "kittens"
Foo.items.include?("kittens").should eq(true)
end
end
class Foo
DEFAULT_ITEMS = %w(foo bar baz)
class << self
attr_accessor :items
end
#items = DEFAULT_ITEMS
end
describe Foo do
before(:each) { Foo.class_variable_set :#items, Foo::DEFAULT_ITEMS }
it "should have a default" do
Foo.items.should eq(Foo::DEFAULT_ITEMS)
end
it "should allow items to be added" do
Foo.items << "kittens"
Foo.items.include?("kittens").should eq(true)
end
end
Or maybe a better way is to reload the class
describe 'items' do
before(:each) do
Object.send(:remove_const, 'Foo')
load 'foo.rb'
end
end
If your class has internal states that you would like to test I find that using the class_variable_get a nice way of approaching this. This does not require you to expose any of the variables in the class, so the class can stay untouched.
it 'increases number by one' do
expect(YourClass.class_variable_get(:##number)).to equal(0)
YourClass.increase_by_one()
expect(YourClass.class_variable_get(:##number)).to equal(1)
end
I know this is not what you ask for in your question, but it is in the title, which got me here.
I found this question pursuing a slightly different problem -- clearing a cached class variable between rspec examples.
In a module, I have an expensive class config, which I cache like this:
module Thingamizer
def config
#config ||= compute_config_the_hard_way()
end
end
class Thing
extend Thingamizer
end
In my rspec tests of Thing, compute_config_the_hard_way was only called the first time. Subsequent calls used the cached version, even if I mock compute_config_the_hard_way to return different things in other tests.
I resolved this by clearing #config before each example:
before { Thing.instance_variable_set(:#config, nil) }
Now the the thing I was hung up on is that #config is a class variable, not an instance variable. I tried many variations of class_variable_set without luck.
The wrinkle here is that Thing (the class) is actually an instance of Class. So what seems to be a class variable in a class method is actually an instance variable, in an instance of Class (i.e. Thing). Once I wrapped my head around that idea, using instance_variable_set instead of class_variable_set made perfect sense.
See Using Instance Variables in Class Methods - Ruby for a discussion of class variables as instance variables.
I'm trying to fill in class variables for a class instance from a file, and the only way I've managed to figure how to do this is something like
a=Thing.new
File.read("filename.ext").each_line do |arguments| #this will => things like #variable=str\n
eval( "a.instance_eval {" + arguments.chop + "}") #hence the awkward eval eval chop
end
The only problem I've found is that in trying to impliment this in a class method (to do this for several instances in a go), I don't know how to make this happen:
class Thing
attr_accessor :variable
def self.method
File.read("filename.ext").each_line do |arguments|
eval("instance.instance_eval{" + arguments.chop + "}") #this line
end
end
end
namely, the reference to the instance calling the method. self will just just be Thing in this case, so is there any way to do this? More pertinent might be a better way to go about this overall. I only just learned ruby last night, so I haven't had an opportunity to see some of the neater tricks that are to be had, and my language maturity is a little fresh yet as a result.
For context, Thing is a character in a game, loading its base values from a savefile.
Well, first off, take a look at Marshal. It's specifically used for dumping data structures to serialized formats and loading them back.
The said, if you want to persist in your direction, then try something like this:
class Thing
attr_accessor :variable
def self.method
File.read("filename.ext").each_line do |arguments|
ivar, val = arguments.strip.split("=", 2)
instance.instance_variable_set(ivar, val)
end
end
end
#instance_variable_set allows you to...well, set instance variables on an object by name. No ugly eval necessary!
By way of demonstration:
class Foo
attr_accessor :bar
end
foo = Foo.new
foo.instance_variable_set("#bar", "whatzits")
puts foo.bar # => whatzits
I'm trying to learn ruby by building a basic Campfire bot to screw around with at work. I've gotten pretty far (it works!) and learned a lot (it works!), but now I'm trying to make it a bit more complex by separating the actions to be performed out into their own classes, so that they can be easier to write / fix when broken. If you're interested in seeing all the (probably crappy) code, it's all up on GitHub. But for the sake of this question, I'll narrow the scope a bit.
Ideally, I would like to be able to create plugins easily, name them the same as the class name, and drop them into an "actions" directory in the root of the project, where they will be instantiated at runtime. I want the plugins themselves to be as simple as possible to write, so I want them all to inherit some basic methods and properties from an action class.
Here is action.rb as it currently exists:
module CampfireBot
class Action
#handlers = {}
def initialize(room)
#room = room
end
class << self
attr_reader :handlers
attr_reader :room
def hear(pattern, &action)
Action.handlers[pattern] = action
end
end
end
end
Where #room is the room object, and #handlers is a hash of patterns and blocks. I kind of don't understand why I have to do that class << self call, but that's the only way I could get the child plugin classes to see that hear method.
I then attempt to create a simple plugin like so (named Foo.rb):
class Foo < CampfireBot::Action
hear /foo/i do
#room.speak "bar"
end
end
I then have my plugins instantiated inside bot.rb like so:
def load_handlers(room)
actions = Dir.entries("#{BOT_ROOT}/actions").delete_if {|action| /^\./.match(action)}
action_classes = []
# load the source
actions.each do |action|
load "#{BOT_ROOT}/actions/#{action}"
action_classes.push(action.chomp(".rb"))
end
# and instantiate
action_classes.each do |action_class|
Kernel.const_get(action_class).new(room)
end
#handlers = Action.handlers
end
The blocks are then called inside room.rb when the pattern is matched by the following:
handlers.each do |pattern, action|
if pattern.match(msg)
action.call($~)
end
end
If I do puts #room inside the initialization of Action, I see the room object printed out in the console. And if I do puts "foo" inside Foo.rb's hear method, I see foo printed out on the console (so, the pattern match is working). But, I can't read that #room object from the parent class (it comes out as a nil object). So obviously I'm missing something about how this is supposed to be working.
Furthermore, if I do something to make the plugin a bit cleaner (for larger functions) and rewrite it like so:
class Foo < CampfireBot::Action
hear /foo/i do
say_bar
end
def say_bar
#room.speak "bar"
end
end
I get NoMethodError: undefined method 'say_bar' for Foo:Class.
The definition of hear can be pulled out of the class << self block and changed to:
def self.hear(pattern, &action)
Action.handlers[pattern] = action
end
to yield the exact same result. That also immediately explains the problem. hear Is a class method. say_bar is an instance method. You can't call an instance method from a class method, because there simply isn't an instance of the class available.
To understand the class << self bit, you'll have to do your own reading and experiments: I won't try to improve on what has already been said. I'll only say that within the class << self .. end block, self refers to the eigenclass or metaclass of the CampfireBot::Action class. This is the instance of the Class class that holds the definition of the CampfireBot::Action class.
I'm really new to Ruby. And by new - less than 16 hours, but my boss gave me some Ruby code to add to. However, I found it was one giant file and not modular at all, so I decided to clean it up. Now that I've broken it up into several files/classes (generally speaking, 1 class per file,) I'm having problems piecing it together for it to work again. Originally everything was part of the same class, so the calls worked, but it looked ugly and it took an entire work day just to figure it out. I want to avoid that for the future as this code will grow much larger before it is done.
My main issue looks like the following (simplified, obviously):
class TestDevice
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(identifier, level, section, message)
...
#loghash[identifier] = { level => { section => message }}
...
end
end
device = TestDevice.new
After that, it calls out to other class methods, and those class methods reference back to the class Log for their logging needs. Of course, Log needs to access "device.loghash" somehow to log the information in that hash. But I can't figure out how to make that happen outside of passing the contents of "loghash" to every method, so that they, in turn, can pass it, and then return the value back to the origination point and then logging it at the end, but that seems really clumsy and awkward.
I'm hoping I am really just missing something.
To create accessors for instance variables the simple way, use attr_accessor.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
You can also manually define an accessor.
class TestDevice
def loghash
#loghash
end
def loghash=(val)
#loghash = val
end
end
This is effectively what attr_accessor does behind the scenes.
how about passing the device object as a parameter to the msg function? (I'm assuming that there can be many devices in your program, otherwise you can use singleton pattern).
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(device, identifier, level, section, message)
...
device.loghash[identifier] = { level => { section => message }}
...
end
end
So you need to learn the rules of ruby scoping.
Ruby variables have different scope, depending on their prefix:
$global_variables start with a $, and are available to everyone.
#instance_variables start with a single #, and are stored with the current value of self. If two
scopes share the same value of self (they're both instance methods, for example),
then both share the same instance variables
##class_variable start with ##, and are stored with the class. They're
shared between all instances of a class - and all instances of subclasses
of that class.
Constants start with a capital letter, and may be all caps. Like class
variables, they're stored with the current self.class, but they also
trickle up the hierarchy - so if you have a class defined in a module,
the instances of the class can access the module's constants as well.
Constants defined outside of a class have global scope.
Note that a constant variable means that which object is bound to the constant
won't change, not that the object itself won't change internal state.
local_variables start with a lowercase letter
You can read more about scope here.
Local variables scoping rules are mainly standard - they're available in
all subscopes of the one in which they are defined except when we move into
a module, class, or method definition. So if we look at your code from your
answer
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
device = TestDevice.new
class Somethingelse
def self.something
device.loghash='something here' # doesn't work
end
end
The scope of the device local variable defined at the toplevel does not include the Somethingelse.something
method definition. So the device local variable used in the Somethingelse.something method definition is a different (empty) variable. If you want the scoping to work that way, you should use a constant or a global variable.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
DEVICE = TestDevice.new
$has_logged = false
class Somethingelse
def self.something
DEVICE.loghash='something here'
$has_logged = true
end
end
p DEVICE.loghash # prints `{}`
p $has_logged # prints `false`
Somethingelse.something
p DEVICE.loghash # prints `"something here"`
p $has_logged # prints `true`