I've been looking at learning a new dynamic scripting language for web development and after agonising over Python and Ruby, as I really liked both, I've decided to pick Ruby (It pretty much came down to a coin toss and the fact there are more RoR jobs in the UK than Python/Django). My question is about scope in Ruby. Do I have to declare a class attribute inside a method to be able to access it from other methods?
For example, I cannot do
class Notes
#notes = ["Pick up some milk"]
def print_notes
puts #notes
end
end
It seems I have to declare attributes I want to use in the constructor? This example works:
class Notes
def initialize
#notes = ["Pick up some milk"]
end
def print_notes
puts #notes
end
end
Is this right? I've noticed prefixing example one with ## instead of # works but to my understanding if the class has a subclass (say, Memo) then any changes to attributes prefixed with ## in Notes would change the value in Memo?
Sorry if this is a repeat question, just a lost noobie :)
When you declare the #notes within the class but not in the constructor or any of the instance methods, then you are making #notes an instance variable of the instance of the class itself. Every class exists as an instance of Class too.
class Notes
#notes = ["Pick up some milk"]
def print_notes
puts #notes
end
end
# => nil
Notes.instance_variable_get(:"#notes")
# => ["Pick up some milk"]
So the answer is yes, you do need to declare the instance variable within the constructor or some other instance method. I think you'd prefer to do this:
class Notes
def notes
#notes ||= []
end
def print_notes
puts #notes
end
end
note = Notes.new
note.notes << "Pick up some milk"
note.notes
# => ["Pick up some milk"]
In addition:
Just avoid class variables e.g. ##notes. Use class instance variables instead (which is what you unwittingly did).
Do this:
class Notes
def self.notes
#notes ||= []
end
end
not this:
class Notes
def notes
##notes ||= []
end
end
when you want a class variable. The latter will cause you problems down the road. (But I think this is something for a different conversation.)
Related
Sorry that I have no clue how to title this, I'm having a hard time looking this up because I don't know how to say this. Anyway...
Let's say I have a class that looks like this for example:
class Run
def self.starting
print "starting..."
end
def self.finished
print "Finished!"
end
end
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting. Now let's say that I wanted to add some instance variables...
class Run
attr_accessor :starting, :finished
def self.starting
print "starting..."
#starting = true
#finished = false
end
def self.finished
print "finished!"
#starting = false
#finished = true
end
end
What if I wanted to access those instance variables from outside the class? I know that something like print "#{Run.finished}" or print "#{Run.starting}" won't do anything. Can I do that without run = Run.new? Or should I just remove self and then use run = Run.new? (Sorry if this question is a mess.)
All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting
There's much more to it than this. In your case you're calling class methods. If you did runner = Runner.new - then you'd be calling instance methods (those are defined without self.
In general, if you need "the thing" to hold some kind of state (like #running = true) then you'd rather want to instantiate an object, and call those methods.
Now, #whatever are instance variables, and you don't have the access to them in class methods.
class Run
attr_reader :running
def start
#running = true
end
def stop
#running = false
end
end
runner = Run.new
runner.running # nil
runner.start
runner.running # true
runner.stop
runner.running # false
I'd recommend you doing some tutorial or basic level book on rails programming, find a chapter about objects and classes. Do some exercises.
In Ruby instance variables are just lexical variables scoped to an instance of a class. Since they are scoped to the instance they always act like a private variable.
If you want to provide access to an instance variable from the outside you create setter and getter methods. Thats what attr_accessor does.
class Person
attr_accessor :name
def initialize(name:)
#name = name
end
def hello
"Hello my name is #{#name}"
end
end
john = Person.new(name: 'John')
john.name = "John Smith"
puts john.hello # "Hello my name is John Smith"
puts john.name # "John Smith"
Methods defined with def self.foo are class methods which are also referred to as singleton methods. You can't access variables belonging to an instance from inside a class method since the recipient when calling the method is the class itself and not an instance of the class.
Ruby also has class variables which are shared by a class and its subclasses:
class Person
##count = 0
def initialize
self.class.count += 1
end
def self.count
##count
end
def self.count=(value)
##count = value
end
end
class Student < Person
end
Person.new
Student.new
puts Person.count # 2 - wtf!
And class instance variables that are not shared with subclasses:
class Person
#count = 0 # sets an instance variable in the eigenclass
def initialize
self.class.count += 1
end
def self.count
#count
end
def self.count=(value)
#count = value
end
end
class Student < Person
#count = 0 # sets its own class instance variable
end
Person.new
Student.new
puts Person.count # 1
Class variables are not used as often and usually hold references to things like database connections or configuration which is shared by all instances of a class.
You can't access instance variables from outside the instance. That is the whole point of instance variables.
The only thing you can access from outside the instance are (public) methods.
However, you can create a public method that returns the instance variable. Such a method is called an attribute reader in Ruby, other languages may call it a getter. In Ruby, an attribute reader is typically named the same as the instance variable, but in your case that is not possible since there are already methods with the names starting and finished. Therefore, we have to find some other names for the attribute readers:
class Run
def self.starting?
#starting
end
def self.finished?
#finished
end
end
Since this is a common operation, there are helper methods which generate those methods for you, for example Module#attr_reader. However, they also assume that the name of the attribute reader method is the same as the name of the instance variable, so if you were to use this helper method, it would overwrite the methods you have already written!
class << Run
attr_reader :starting, :finished
end
When you do this, you will get warnings (you always have warning turned on when developing, do you?) telling you that you have overwritten your existing methods:
run.rb:19: warning: method redefined; discarding old starting
run.rb:2: warning: previous definition of starting was here
run.rb:19: warning: method redefined; discarding old finished
run.rb:5: warning: previous definition of finished was here
I have an issue I have been whacking my head against for hours now, and neither I nor anyone I have asked has been able to come up with a suitable answer.
Essentially, I am writing a method that allows me to edit an instance variable of another method. I have multiple ways of doing this, however my issue is with writing the test for this method. I have tried many different double types, however as they are immutable and do not store states, I did not manage to find a way to make it work.
Here is the class whose working variable is changed:
class MyClass
attr_writer :working
def working?
#working
end
end
Here is the class and method that change it:
class OtherClass
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
end
end
(The actual class is much larger, but I have only included a generalised version of the method in question. I can put all of the specific code up in a gist if it would help)
So I need a way to test that makes_work does in fact accept the array of objects to be changed, changes them and appends them to array_of_fixed_objects. What would be the best way of testing this in a containerised way, without requiring MyClass?
My last attempt was using spies to see what methods were called on my dummy instance, however a range of failures, depending on what I did. Here is the most recent test I wrote:
describe '#make_work' do
it 'returns array of working instances' do
test_obj = spy('test_obj')
subject.ary_of_instances_of_MyClass_to_fix = [test_obj]
subject.makes_work
expect(test_obj).to have_received(working = true)
end
end
This currently throws the error:
undefined method to_sym for true:TrueClass
Many thanks for any help! I apologise if some formatting/ info is a little bit messed up, I am still pretty new to this whole stackoverflow thing!
I think the problem is have_received(working = true), it should be have_received(:working=).with(true)
Edit:
Examples of using have_received
https://github.com/rspec/rspec-mocks#test-spies
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/setting-constraints/matching-arguments
This works for me
class MyClass
attr_writer :working
def working?
#working
end
end
class OtherClass
attr_writer :ary_of_instances_of_MyClass_to_fix
def initialize
#ary_of_fixed_objects = []
end
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
end
end
describe '#make_work' do
subject { OtherClass.new }
it 'returns array of working instances' do
test_obj = spy('test_obj')
subject.ary_of_instances_of_MyClass_to_fix = [test_obj]
subject.makes_work
expect(test_obj).to have_received(:working=).with(true)
end
end
If you'd rather just avoid stubbing, you could use an instance of OpenStruct instead of a double:
class OtherClass
attr_writer :ary_of_instances_of_MyClass_to_fix
def initialize
#ary_of_instances_of_MyClass_to_fix, #ary_of_fixed_objects = [], []
end
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
#ary_of_fixed_objects
end
end
require 'ostruct'
RSpec.describe "#makes_work" do
describe "given an array" do
let(:array) { [OpenStruct.new(working: nil)] }
subject { OtherClass.new }
before do
subject.ary_of_instances_of_MyClass_to_fix = array
end
it "sets the 'working' attribute for each element" do
expect(array.map(&:working)).to eq [nil]
subject.makes_work
expect(array.map(&:working)).to eq [true]
end
end
end
I would like to access a class' name in its superclass MySuperclass' self.inherited method. It works fine for concrete classes as defined by class Foo < MySuperclass; end but it fails when using anonymous classes. I tend to avoid creating (class-)constants in tests; I would like it to work with anonymous classes.
Given the following code:
class MySuperclass
def self.inherited(subclass)
super
# work with subclass' name
end
end
klass = Class.new(MySuperclass) do
def self.name
'FooBar'
end
end
klass#name will still be nil when MySuperclass.inherited is called as that will be before Class.new yields to its block and defines its methods.
I understand a class gets its name when it's assigned to a constant, but is there a way to set Class#name "early" without creating a constant?
I prepared a more verbose code example with failing tests to illustrate what's expected.
Probably #yield has taken place after the ::inherited is called, I saw the similar behaviour with class definition. However, you can avoid it by using ::klass singleton method instead of ::inherited callback.
def self.klass
#klass ||= (self.name || self.to_s).gsub(/Builder\z/, '')
end
I am trying to understand the benefit of being able to refer to an anonymous class by a name you have assigned to it after it has been created. I thought I might be able to move the conversation along by providing some code that you could look at and then tell us what you'd like to do differently:
class MySuperclass
def self.inherited(subclass)
# Create a class method for the subclass
subclass.instance_eval do
def sub_class() puts "sub_class here" end
end
# Create an instance method for the subclass
subclass.class_eval do
def sub_instance() puts "sub_instance here" end
end
end
end
klass = Class.new(MySuperclass) do
def self.name=(name)
#name = Object.const_set(name, self)
end
def self.name
#name
end
end
klass.sub_class #=> "sub_class here"
klass.new.sub_instance #=> "sub_instance here"
klass.name = 'Fido' #=> "Fido"
kn = klass.name #=> Fido
kn.sub_class #=> "sub_class here"
kn.new.sub_instance #=> "sub_instance here"
klass.name = 'Woof' #=> "Woof"
kn = klass.name #=> Fido (cannot change)
There is no way in pure Ruby to set a class name without assigning it to a constant.
If you're using MRI and want to write yourself a very small C extension, it would look something like this:
VALUE
force_class_name (VALUE klass, VALUE symbol_name)
{
rb_name_class(klass, SYM2ID(symbol_name));
return klass;
}
void
Init_my_extension ()
{
rb_define_method(rb_cClass, "force_class_name", force_class_name, 1);
}
This is a very heavy approach to the problem. Even if it works it won't be guaranteed to work across various versions of ruby, since it relies on the non-API C function rb_name_class. I'm also not sure what the behavior will be once Ruby gets around to running its own class-naming hooks afterward.
The code snippet for your use case would look like this:
require 'my_extension'
class MySuperclass
def self.inherited(subclass)
super
subclass.force_class_name(:FooBar)
# work with subclass' name
end
end
I want something like the following but would like it to be reusable for different classes.
How do I refactor this code, so with minimal effort it can be included in a class and that class will automatically be collecting instances whenever new is called?
I've tried all sorts of things like overriding new or initialize but just can't get the magic to happen.
class Person
##people_instances = []
def initialize
##people_instances << self
end
def self.instances
##people_instances
end
end
People.new
People.new
Poople.instances
=> [#<Person:0x000001071a7e28>, #<Person:0x000001071a3828>]
After some feedback below, I don't think the answer is to put the instances in a class variable as it will stay in memory forever. Rails cache is also not so appropriate as I don't need the instances to persist.
The following code uses class instance variables instead of class variables.
http://www.dzone.com/snippets/class-variables-vs-class
class Employee
class << self; attr_accessor :instances; end
def store
self.class.instances ||= []
self.class.instances << self
end
def initialize name
#name = name
end
end
class Overhead < Employee; end
class Programmer < Employee; end
Overhead.new('Martin').store
Overhead.new('Roy').store
Programmer.new('Erik').store
puts Overhead.instances.size # => 2
puts Programmer.instances.size # => 1
Will these instance variables be unique to every rails request or will they persist?
UPDATED ANSWER
If you want to keep it available during the request alone, none of the previous answers can do it. The solution for keeping it available only during the request-response cycle is to use a thread-local that is assigned in a controller method, example:
class YourController < ApplicationController
around_filter :cache_objects
protected
def cache_objects
Thread.current[:my_objects] = ['my-object', 'my-other-object']
yield
ensure
Thread.current[:my_objects]
end
end
Then, at the code that needs it, you just do Thread.current[:my_objects] and do whatever you would like to do with them. You need to use an around_filter because your web framework or server structure could try to reuse threads and the only real solution is to clean them up once the request is done to avoid memory leaks.
OLD ANSWER
Not sure what you're trying to do, but you can easily pick every single instance of a class using ObjectSpace:
ObjectSpace.each_object(String) { |s| puts(s) }
If what you need is as a database cache just use the Rails cache, load these objects once and then keep them in the cache. When using the Rails cache all you need to do is send your objects to the cache:
Rails.cache.write( "my_cached_objects", [ 'first-object', 'second-object' ] )
And then get them somewhere else:
Rails.cache.fetch("my_cached_objects") do
# generate your objects here if there was a cache miss
[ 'first-object', 'second-object' ]
end
As you can see, you don't even have to call cache.write, you can just use fetch and whenever there is a cache miss the block given will be called and your objects will be created.
You can read more about rails caching here and you can see all supported methods of the ActiveSupport::Cache::Store here.
Another method without using ObjectSpace but still with an ugly solution, now using alias_method:
module Counter
def self.included( base )
base.extend(ClassMethods)
base.class_eval do
alias_method :initialize_without_counter, :initialize
alias_method :initialize, :initialize_with_counter
end
end
def count_class_variable_name
:"###{self.class.name.downcase}_instances"
end
def initialize_with_counter( *args )
unless self.class.class_variable_defined?(count_class_variable_name)
self.class.class_variable_set(count_class_variable_name, [])
end
self.class.class_variable_get(count_class_variable_name) << self
initialize_without_counter(*args)
end
module ClassMethods
def all_instances
class_variable_get(:"###{name.downcase}_instances")
end
end
end
class Person
def initialize
puts 'new person'
end
include Counter
end
p1 = Person.new
p2 = Person.new
p3 = Person.new
puts Person.all_instances.size
lib/keeper.rb
def initialize
instance_eval "###{self.class.to_s.downcase}_instances ||= []"
instance_eval "###{self.class.to_s.downcase}_instances << self"
end
def self.instances
return class_eval "###{self.to_s.downcase}_instances"
end
person.rb
class Person
eval File.open('./lib/keeper.rb','rb').read
end
Then this works:
Person.new
Person.new
Person.instances
I am trying to build a simple little template parser for self-learning purposes.
How do I build something "modular" and share data across it? The data doesn't need to be accessible from outside, it's just internal data. Here's what I have:
# template_parser.rb
module TemplateParser
attr_accessor :html
attr_accessor :test_value
class Base
def initialize(html)
#html = html
#test_value = "foo"
end
def parse!
#html.css('a').each do |node|
::TemplateParser::Tag:ATag.substitute! node
end
end
end
end
# template_parser/tag/a_tag.rb
module TemplateParser
module Tag
class ATag
def self.substitute!(node)
# I want to access +test_value+ from +TemplateParser+
node = #test_value # => nil
end
end
end
end
Edit based on Phrogz' comment
I am currently thinking about something like:
p = TemplateParser.new(html, *args) # or TemplateParser::Base.new(html, *args)
p.append_css(file_or_string)
parsed_html = p.parse!
There shouldn't be much exposed methods because the parser should solve a non-general problem and is not portable. At least not at this early stage. What I've tried is to peek a bit from Nokogiri about the structure.
With the example code you've given, I'd recommend using composition to pass in an instance of TemplateParser::Base to the parse! method like so:
# in TemplateParser::Base#parse!
::TemplateParser::Tag::ATag.substitute! node, self
# TemplateParser::Tag::ATag
def self.substitute!(node, obj)
node = obj.test_value
end
You will also need to move the attr_accessor calls into the Base class for this to work.
module TemplateParser
class Base
attr_accessor :html
attr_accessor :test_value
# ...
end
end
Any other way I can think of right now of accessing test_value will be fairly convoluted considering the fact that parse! is a class method trying to access a different class instance's attribute.
The above assumes #test_value needs to be unique per TemplateParser::Base instance. If that's not the case, you could simplify the process by using a class or module instance variable.
module TemplateParser
class Base
#test_value = "foo"
class << self
attr_accessor :test_value
end
# ...
end
end
# OR
module TemplateParser
#test_value = "foo"
class << self
attr_accessor :test_value
end
class Base
# ...
end
end
Then set or retrieve the value with TemplateParser::Base.test_value OR TemplateParser.test_value depending on implementation.
Also, to perhaps state the obvious, I'm assuming your pseudo-code you've included here doesn't accurately reflect your real application code. If it does, then the substitute! method is a very round about way to achieve simple assignment. Just use node = test_value inside TemplateParser::Base#parse! and skip the round trip. I'm sure you know this, but it seemed worth mentioning at least...