Ruby scope question, option parsing - ruby

So I'm using the awesome trollop gem to do option parsing, but I'm having a general problem with the scope of the variables it's setting.
require 'trollop'
class MyClass
opts = Trollop::options do
opt :thing, "does something", default: "blah", type: String
end
def my_method
puts opts[:thing]
end
end
But I get:
undefined local variable or method `opts' for #<MyClass:0x0000010203c840> (NameError)
Any ideas what I'm doing wrong with my scope?

There are about six options here: instance variable, class instance variable, class variable, class constant, global variable, global constant. Which to use depends on your needs.
Instance Variable - each MyClass instance gets its own options:
class MyClass
def initialize
#opts = ...
end
def my_method
puts #opts[:thing]
end
end
Class Instance Variable - single value across the class that can be reassigned:
class MyClass
#opts = ...
class << self
attr_accessor :opts
end
def my_method
puts self.class.opts[:thing]
end
end
Class Variable - each MyClass and all subclasses share the same value (convenient syntax, but rarely a good idea):
class MyClass
##opts = ...
def my_method
puts ##opts[:thing]
end
end
Class Constant - single object that may be mutated, but not re-assigned. Easily accessed from this class, accessible from others via MyClass::OPTS:
class MyClass
OPTS = ...
def my_method
puts OPTS[:thing]
end
end
Global Variable - you can only have one of these in your entire app; often global variables are ill-advised, but perhaps appropriate for a standalone application's options:
$opts = ...
class MyClass
def my_method
puts $opts[:thing]
end
end
Global Constant - accessed from many classes, can't be set to a new value, but may be mutated:
OPTS = ...
class MyClass
def my_method
puts OPTS[:thing]
end
end

Shouldn't you just use instance variable?
require 'trollop'
class MyClass
def initialize
#opts = Trollop::options do
opt :thing, "does something", default: "blah", type: String
end
end
def my_method
puts #opts[:thing]
end
end

You are defining 'opts' as a local variable inside your class. Instances methods (like my_method) will not be able to access it. Is opts supposed to be "global" for the whole class? In that case:
class MyClass
##opts = Trollop::options...
def my_method
puts ##opts[:thing]
end
end
Or is there supposed to be a unique one for each instance of the class?
class MyClass
def initialize
#opts = Trollop::options...
end
def my_method
puts #opts[:thing]
end
end
This might be a good read: http://sporkmonger.com/2007/2/19/instance-variables-class-variables-and-inheritance-in-ruby

You'd want to make it either a class variable or an instance variable, depending on your needs.

Related

Ruby: How to reference a variable defined outside of a module

How do I pass processor_pool to the method inside the module?
class Dummy
def initialize
processor_pool = Concurrent::FixedThreadPool.new(10)
#threadpool = Module.new do
extend Concurrent::Promises::FactoryMethods
def self.default_executor
return processor_pool # this cannot find the processor_pool variable
end
end
end
end
I get the same error even if I make it an instance variable like #processor_pool
Something like this (I simplified your class a bit to get rid of dependencies for the sake of example, but its structure is the same):
class Dummy
attr_reader :threadpool
def initialize
processor_pool = "It works"
#threadpool = Module.new do
define_method :default_executor do
return processor_pool
end
module_function :default_executor
end
end
end
Dummy.new.threadpool.default_executor # => "It works"

Attr_accessor on class variables

attr_accessor does not work on the following code. The error says "undefined method 'things' for Parent:Class (NoMethodError)":
class Parent
##things = []
attr_accessor :things
end
Parent.things << :car
p Parent.things
However the following code works
class Parent
##things = []
def self.things
##things
end
def things
##things
end
end
Parent.things << :car
p Parent.things
attr_accessor defines accessor methods for an instance. If you want class level auto-generated accessors you could use it on the metaclass
class Parent
#things = []
class << self
attr_accessor :things
end
end
Parent.things #=> []
Parent.things << :car
Parent.things #=> [:car]
but note that this creates a class level instance variable not a class variable. This is likely what you want anyway, as class variables behave differently than you might expect when dealing w/ inheritance. See "Class and Instance Variables In Ruby".
attr_accessor generates accessors for instance variables. Class variables in Ruby are a very different thing, and they are usually not what you want. What you probably want here is a class instance variable. You can use attr_accessor with class instance variables like so:
class Something
class << self
attr_accessor :things
end
end
Then you can write Something.things = 12 and it will work.
Just some clarification: class variables won't be accessible using attr_accessor. It's all about instance variables:
class SomeClass
class << self
attr_accessor :things
end
#things = []
end
because in Ruby, class is an instance of the class "Class" (God, I love to say that) and attr_accessor sets accessor methods for instance variables.
This is probably the simplest way.
class Parent
def self.things
##things ||= []
end
end
Parent.things << :car
p Parent.things
Аlso note that a singleton method is a method only for a single object. In Ruby, a Class is also an object, so it too can have singleton methods! So be aware of when you might be calling them.
Example:
class SomeClass
class << self
def test
end
end
end
test_obj = SomeClass.new
def test_obj.test_2
end
class << test_obj
def test_3
end
end
puts "Singleton methods of SomeClass"
puts SomeClass.singleton_methods
puts '------------------------------------------'
puts "Singleton methods of test_obj"
puts test_obj.singleton_methods
Singleton methods of SomeClass
test
Singleton methods of test_obj
test_2
test_3
Parent.class_variable_get(:##things)
That would be the built-in way. In most cases this should be sufficient I think. No need to have a class variable accessor in the instance.
class Parent
#things = []
singleton_class.send(:attr_accessor, :things)
end
This pattern is most useful when you are defining accessors dynamically or creating them inside a method:
class Foo
def self.add_accessor(name)
singleton_class.send(:attr_accessor, name)
end
end
Foo.add_accessor :things
Foo.things = [:car]
Foo.things # => [:car]

Create attr_reader methods while setting the values for the according instance variables in the initialize method?

I am looking for way to create the according attr_reader methods while setting the values for the according instance variables in the initialize method? For example, the following code:
class SomeClass
attr_reader :hello
def initialize( arg)
#hello = arg
end
end
I am looking for way to write as follows:
class SomeClass
def initialize( arg)
some_method_as_described_in_question( #hello, arg)
end
end
Does a method doing what I have described exist in the Ruby built-in Classes and Modules?
You can open the eigenclass from within the method and set the attribute there:
class SomeClass
def initialize(arg)
(class << self; self; end).send(:attr_reader, :hello)
#hello = arg
end
end
That way each instance's eigenclass will have that attribute reader. But really it only makes sense to do things that way if the attribute name is dynamic, and can vary from instance to instance. If it's always hello, I don't see any drawback to just defining it in the class like your original code block.
For example, if you are dynamically passing in the attribute name, you could do it like this:
class SomeClass
def initialize(attr, arg)
(class << self; self; end).send(:attr_reader, attr.to_sym)
instance_variable_set("##{attr}", arg)
end
end
This is compatible with Ruby 1.8. Taking a tip from #HenrikN in the comment to your question, you can use define_singleton_method in Ruby 1.9:
class SomeClass
def initialize(attr, arg)
define_singleton_method(attr) { instance_variable_get("##{attr}") }
instance_variable_set("##{attr}", arg)
end
end
Not sure if I understand the question, but you can use Struct to get an initializer and accessor methods:
class SomeClass < Struct.new(:hello)
end
x = SomeClass.new("yo")
puts x.hello # "yo"
x.hello = "what up"
puts x.hello # "what up"
require 'ostruct'
p = OpenStruct.new
p.hello = 'world'
p.could_be_anything = 'nothing'
puts p.hello #=> 'world'
puts p.could_be_anything #=> 'nothing'

How can a class method (inside a module) update an instance variable?

How can a class method (inside a module) update an instance variable? Consider the code bellow:
module Test
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def update_instance_variable
#temp = "It won't work, bc we are calling this on the class, not on the instance."
puts "How can I update the instance variable from here??"
end
end
end
class MyClass
include Test
attr_accessor :temp
update_instance_variable
end
m = MyClass.new # => How can I update the instance variable from here??
puts m.temp # => nil
You'd have to pass your object instance to the class method as a parameter, and then return the updated object from the method.
That does nto quite make sense.
You use the initialize method to set default values.
class MyClass
attr_accessor :temp
def initialize
#temp = "initial value"
end
end
The initialize method is automatically run for you when you create a new object.
When your class declaration is run, there are no, and cannot be any, instances of the class yet.
If you want to be able to change the default values later you can do something like this:
class MyClass
attr_accessor :temp
##default_temp = "initial value"
def initialize
#temp = ##default_temp
end
def self.update_temp_default value
##default_temp = value
end
end
a = MyClass.new
puts a.temp
MyClass.update_temp_default "hej"
b = MyClass.new
puts b.temp
prints
initial value
hej
If you also want that to change already created instances' variables you need additional magic. Please explain exactly what you wish to accomplish. You are probably doing it wrong :)

Why can't I use attr_accessor inside initialize?

I'm trying to do an instance_eval followed by a attr_accessor inside initialize, and I keep getting this: ``initialize': undefined method 'attr_accessor'`. Why isn't this working?
The code looks kind of like this:
class MyClass
def initialize(*args)
instance_eval "attr_accessor :#{sym}"
end
end
You can't call attr_accessor on the instance, because attr_accessor is not defined as an instance method of MyClass. It's only available on modules and classes. I suspect you want to call attr_accessor on the instance's metaclass, like this:
class MyClass
def initialize(varname)
class <<self
self
end.class_eval do
attr_accessor varname
end
end
end
o1 = MyClass.new(:foo)
o2 = MyClass.new(:bar)
o1.foo = "foo" # works
o2.bar = "bar" # works
o2.foo = "baz" # does not work
A cleaner implementation (NB: This will add the accessor to ALL instances of the class, not just the single instance, see comments below):
class MyClass
def initialize(varname)
self.class.send(:attr_accessor, varname)
end
end
Rob d'Apice has it almost right. You just need to write:
self.singleton_class.send(:attr_accessor, varname)
or
self.singleton_class.class_eval "attr_accessor :#{varname}"
or my favorite variant
self.singleton_class.class_exec do attr_accessor varname end
assuming the value of varname is a symbol

Resources