I am trying to learn Ruby by reading tutorials on Class Variables.
The code creates a "Worker" object, a class variable is created to keep track of which instance of Worker was created by the user last.
I have copied the code from the author but I get the error:
undefined method `latest' for Worker:Class (NoMethodError)
The code I have found is:
class Worker
attr_writer :number_of_jobs
def initialize(name, job)
#name = name
#job = job
##latest = #name
##job = #job
puts "Lets get started"
end
def new_job(job)
#job = job
puts "I moved to #{job}!"
self.fetch_info
end
def name_update(name_new)
#name = name_new
puts "My new name is #{name_new}."
self.fetch_info
end
def fetch_info
puts "I'm #{#name} in #{#location}."
end
def job_score
return "#{#number_of_jobs * 10}Fg"
end
protected
def are_you_worker?(guest_name)
puts "Yes #{guest_name}, I am a worker!"
return true
end
private
def text_a_message(message)
puts message
end
public
def tell_friend(where)
text_a_message("I just applied to #{where}")
end
end
#running the code
Worker1 = Worker.new("Steve", "Support")
Worker2 = Worker.new("Alan", "PA")
puts Worker.latest
Can anybody see why?
The Class variables are private inside this class which is causing a problem. Therefore accessing the Worker.latest variable will cause an error as it isn't visible from instances outside of the class (but it is created and set).
Additionally, attributes are part of the object not the class so you shouldn't have an attribute for this class . In native Ruby the class variables are not accessible from outside EXCEPT through a class method (there are extensions in Rails for them tho).
Hope that helps
IMHO, this is one of the more frustrating things about Ruby's class system. The privacy of class variables is true even for subclasses. In any case, the immediate solution to your problem is to add a class method like so:
class Worker
def self.latest
##latest
end
end
Related
i am trying to understand the .self pointer , class and instance variable and their uses. I found many useful links but nothing seems to get into my head. like this medium post ::
medium.com - #/## vs. self in Ruby
here is the code I tried (filename = hello.rb)
class Person
def instance_variable_get
#instance_var = "instance variable"
end
def class_var
##class_var = "class variable"
end
def sayhi
puts "this is a ins #{#instance_var}"
puts "this is a cls #{class_var}" #note i removed the # sign from this.
end
def self.sayhi
puts "this is a ins from class #{#instance_var}"
puts "this is a cls from class #{#class_var}"
end
end
bob = Person.new
bob.sayhi
Person.sayhi
and by executing this i got
cd#CD:~/Desktop$ ruby hello.rb
this is a ins
this is a cls class variable
this is a ins from class
this is a cls from class
how does this all thing work? what am I doing wrong?
the result I am expecting from this is
this is a ins instance variable
this is a cls class variable
this is a ins from class instance variable
this is a cls from class class variable
That code doesn't actually work since you're not calling any of methods that set the variables. Its kind of a mess and I think I can explain this better with more idiomatic examples.
In Ruby instance variables are just lexically scoped local variables that are scoped to an object instance. They are always "private" but you can provide accessor methods to provide access from the outside (and the insider also):
class Person
def initialize(name)
#name = name
end
def name=(value)
#name = name
end
def name
#name
end
def hello
"Hello, my name is #{name}"
end
end
jane = Person.new("Jane")
puts jane.hello # "Hello, my name is Jane"
jane.name = "Jaayne"
puts jane.hello # "Hello, my name is Jaayne"
Inside the hello method self is the instance of Person that you are calling the the method on.
We can simply call name instead of self.name since its the implicit reciever. We could also write "Hello, my name is #{#name}" and it would give the exact same result since the getter method is just returning the instance variable.
Ending setter methods with = is just a convention that lets you use the method with the = operator. You can actually set instance variables from any method.
class Person
attr_accessor :name
def initialize(name)
#name = name
end
def backwardize!
#name = #name.reverse
end
end
Class variables on the other hand are a whole different cup of tea. The scope of a class variable is a class - but the are also shared with any subclasses. This is an example of why they are best avoided:
class Vehicle
def self.number_of_wheels=(value)
##number_of_wheels = value
end
def self.number_of_wheels
##number_of_wheels
end
end
class Car < Vehicle
self.number_of_wheels = 4
end
puts Car.number_of_wheels # 4 - good
class Bike < Vehicle
self.number_of_wheels = 2
end
puts Bike.number_of_wheels # 2 - good
puts Car.number_of_wheels # 2 - WAAAT?!
Setting class variables from an instance method which you have done in your example is not commonly done. If you have an instance method that changes the behavior of all other instances of the same class it will often lead to a large amount of swearing.
Instead of class variables use class instance variables:
class Vehicle
def self.number_of_wheels=(value)
#number_of_wheels = value
end
def self.number_of_wheels
#number_of_wheels
end
end
class Car < Vehicle
# you need to explicitly use self when calling setter methods
# otherwise Ruby will think you're setting a local variable.
self.number_of_wheels = 4
end
puts Car.number_of_wheels # 4 - good
class Bike < Vehicle
self.number_of_wheels = 2
end
puts Bike.number_of_wheels # 2 - good
puts Car.number_of_wheels # 4 - good
This can be a mind boggling concept but just try to remember that in Ruby classes are just instances of Class.
You also seem to be somewhat confused about what instance_variable_get whould be used for. Its used to violate encapsulation and get the instance variables of an object from the outside.
class Foo
def initialize
#bar = "I'm Foo's secret"
end
end
puts Foo.new.instance_variable_get(:#bar) # "I'm Foo's secret"
Violating encapsulation should not normally be how you structure your code but it can be very useful in some circumstances. It is not called when accessing instance variables from within an object. I don't think I have ever seen anyone redefine the method - just because you can doesn't mean you should.
Because the functions which assign values to variables are not called at all. Do something like this. This ensures the variable values are set when you run a method from object or class:
class Person
#instance_var = "instance variable"
##class_var = "class variable"
def sayhi
puts "this is a ins #{#instance_var}"
puts "this is a cls #{class_var}" #note i removed the # sign from this.
end
def self.sayhi
puts "this is a ins from class #{#instance_var}"
puts "this is a cls from class #{#class_var}"
end
end
bob = Person.new
bob.sayhi
Person.sayhi
Additionally refer to this: Ruby class instance variable vs. class variable
Is there a way to implement monkey patching while an object is being instantiated?
When I call:
a = Foo.new
Prior to the instance being instantiated, I would like to extend the Foo class based on information which I will read from a data store. As such, each time I call Foo.new, the extension(s) that will be added to that instance of the class would change dynamically.
tl;dr: Adding methods to an instance is possible.
Answer: Adding methods to an instance is not possible. Instances in Ruby don't have methods. But each instance can have a singleton class, where one can add methods, which will then be only available on the single instance that this singleton class is made for.
class Foo
end
foo = Foo.new
def foo.bark
puts "Woof"
end
foo.bark
class << foo
def chew
puts "Crunch"
end
end
foo.chew
foo.define_singleton_method(:mark) do
puts "Widdle"
end
foo.mark
are just some of the ways to define a singleton method for an object.
module Happy
def cheer
puts "Wag"
end
end
foo.extend(Happy)
foo.cheer
This takes another approach, it will insert the module between the singleton class and the real class in the inheritance chain. This way, too, the module is available to the instance, but not on the whole class.
Sure you can!
method_name_only_known_at_runtime = 'hello'
string_only_known_at_runtime = 'Hello World!'
test = Object.new
test.define_singleton_method(method_name_only_known_at_runtime) do
puts(string_only_known_at_runtime)
end
test.hello
#> Hello World!
Prior to the instance being instantiated, I would like to extend
Given a class Foo which does something within its initialize method:
class Foo
attr_accessor :name
def initialize(name)
self.name = name
end
end
And a module FooExtension which wants to alter that behavior:
module FooExtension
def name=(value)
#name = value.reverse.upcase
end
end
You could patch it via prepend:
module FooPatcher
def initialize(*)
extend(FooExtension) if $do_extend # replace with actual logic
super
end
end
Foo.prepend(FooPatcher)
Or you could extend even before calling initialize by providing your own new class method:
class Foo
def self.new(*args)
obj = allocate
obj.extend(FooExtension) if $do_extend # replace with actual logic
obj.send(:initialize, *args)
obj
end
end
Both variants produce the same result:
$do_extend = false
Foo.new('hello')
#=> #<Foo:0x00007ff66582b640 #name="hello">
$do_extend = true
Foo.new('hello')
#=> #<Foo:0x00007ff66582b280 #name="OLLEH">
This is running in multiple Sidekiq instances and workers at the same time and it seems that is has generated a couple of issues, like instances getting assigned the "It was alerted recently" error when shouldn't and the opposite.
It is rare, but it is happening, is this the problem or maybe it is something else?
class BrokenModel < ActiveRecord::Base
validates_with BrokenValidator
end
class BrokenValidator < ActiveModel::Validator
def validate record
#record = record
check_alerted
end
private
def check_alerted
if AtomicGlobalAlerted.new(#record).valid?
#record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{#record.errors[:base]}"
end
end
class AtomicGlobalAlerted
include Redis::Objects
attr_accessor :id
def initialize id
#id = id
#fredis = nil
Sidekiq.redis do |redis|
#fredis = FreshRedis.new(redis, freshness: 7.days, granularity: 4.hours)
end
end
def valid?
#fredis.smembers.includes?(#id)
end
end
We were experiencing something similar at work and after A LOT of digging finally figured out what was happening.
The class method validates_with uses one instance of the validator (BrokenValidator) to validate all instances of the class you're trying to validate (BrokenModel). Normally this is fine but you are assigning a variable (#record) and accessing that variable in another method (check_alerted) so other threads are assigning #record while other threads are still trying to check_alerted.
There are two ways you can fix this:
1) Pass record to check_alerted:
class BrokenValidator < ActiveModel::Validator
def validate(record)
check_alerted(record)
end
private
def check_alerted(record)
if AtomicGlobalAlerted.new(record).valid?
record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{record.errors[:base]}"
end
end
2) Use the instance version of validates_with which makes a new validator instance for each model instance you want to validate:
class BrokenModel < ActiveRecord::Base
validate :instance_validators
def instance_validators
validates_with BrokenValidator
end
end
Either solution should work and solve the concurrency problem. Let me know if you experience any other issues.
I believe there are some thread safety issues in rails but we can overcome them by taking necessary precautions.
The local variables, such as your local var, are local to each particular invocation of the method block. If two threads are calling this block at the same time, then each call will get its own local context variable and those won't overlap unless there are shared resources involved: instance variables like (#global_var), static variables (##static_var), globals ($global_var) can cause concurrency problems.
You are using instance variable, just instantiate it every time you are coming to the validate_record method and hopefully your problem will go away like :
def validate record
#record.errors[:base] = []
#record = record
check_alerted
end
For more details you can visit this detailed link
Or try to study about rails configs here : link
This is my first question at
I'm quite new to RoR and I try to understand the PINGOWebApp, which you can find here https://github.com/PingoUPB/PINGOWebApp.
They specified their "question" model (app/models/question.rb) in different types of questions in app/services/ (e.g. number_question.rb, text_question.rb), all inheriting from app/services/generic_question.rb:
class GenericQuestion < Delegator
def initialize(question)
super
#question = question
end
def __getobj__ # required
#question
end
def __setobj__(obj)
#question = obj # change delegation object
end
def to_model
#question.to_model
end
def has_settings?
false
end
def add_setting(key, value)
#question.settings ||= {}
#question.settings[key.to_s] = value.to_s
end
def self.model_name
Question.model_name
end
def self.reflect_on_association arg
Question.reflect_on_association arg
end
alias_method :question, :__getobj__ # reader for survey
end
Here comes my first questions:
Since there's no service generator, they must have created all the ruby-files in app/service/ by hand, haven't they? Or what other ways are there?
I forked the project and added another service by hand, called dragdrop_question.rb, and integrated it into the question_controller.rb:
class QuestionsController < ApplicationController
def new
#question_single = SingleChoiceQuestion.new.tap { |q| q.question_options.build }
#question_multi = MultipleChoiceQuestion.new.tap { |q| q.question_options.build }
#question_text = TextQuestion.new
#question_number = NumberQuestion.new #refactor this maybe?
#question_dragdrop = DragDropQuestion.new.tap { |q| q.answer_pairs.build }
end
end
I also adapted the view and tested it locally. I got NameError at /questions/new
uninitialized constant QuestionsController::DragDropQuestion.
If I add
require_dependency "app/services/dragdrop_question.rb"
to the question_controller.rb the error is gone, but they haven't done it anything like that. So how do they introduce the services to controllers?
Thanks for any help in advance, especially for tutorials or book references what explain the controller-model-view-service schema.
Try to follow the right naming convention, your class name is DragDropQuestion therefore the expected file name is drag_drop_question.rb.
I have a class like so:
Railsapp/lib/five9_providers/record_provider.rb:
class Five9Providers::RecordProvider < Five9Providers::BaseProvider
def add_record_to_list
variable = 'test'
end
end
Then, in a controller I have this:
Railsapp/app/controllers/five9_controller.rb:
class Five9Controller < ApplicationController
def import
record_provider = Five9Providers::RecordProvider.new()
record_provider.add_record_to_list
puts Five9Providers::RecordProvider::variable
end
end
However, calling my controller method import just returns:
NoMethodError (undefined method 'variable' for Five9Providers::RecordProvider:Class)
How can I access variable from the recover_provider.rb class in my five9_controller.rb class?
EDIT:
Even when using ##variable in both my record_provider and my five9_controller, I still can't access that variable. I am calling it like so: puts ##variable.
As written, you cannot. variable is local to the instance method and can't be accessed by any Ruby expression from outside the method.
On a related point, the term "class variable" is typically used to refer to variables of the form ##variable.
Update: In response to your "Edit" statement, if you change variable to ##variable in your class, then there are techniques available to access that variable from outside the class, but a naked reference to ##variable isn't one of them. Carefully read the answers to the question you cited in your comment for more information.
Best way is to set and get the value using methods. Below is a sample code
class Planet
##planets_count = 0
def initialize(name)
#name = name
##planets_count += 1
end
def self.planets_count
##planets_count
end
def self.add_planet
##planets_count += 1
end
def add_planet_from_obj
##planets_count += 1
end
end
Planet.new("uranus")
Plant.add_planet
obj = Planet.new("earth")
obj.add_planet_from_obj