Ruby class instance variables in subclasses - ruby

Relying on this answer, I wrote the following class. When using it, I get an error:
in 'serialize': undefined method '[]=' for nil:NilClass (NoMethodError).
How can I access the variable #serializable_attrs in a base class?
Base class:
# Provides an attribute serialization interface to subclasses.
class Serializable
#serializable_attrs = {}
def self.serialize(name, target=nil)
attr_accessor(name)
#serializable_attrs[name] = target
end
def initialize(opts)
opts.each do |attr, val|
instance_variable_set("##{attr}", val)
end
end
def to_hash
result = {}
self.class.serializable_attrs.each do |attr, target|
if target != nil then
result[target] = instance_variable_get("##{attr}")
end
end
return result
end
end
Usage example:
class AuthRequest < Serializable
serialize :company_id, 'companyId'
serialize :private_key, 'privateKey'
end

Class instance variables are not inherited, so the line
#serializable_attrs = {}
Only sets this in Serializable not its subclasses. While you could use the inherited hook to set this on subclassing or change the serialize method to initialize #serializable_attrs I would probably add
def self.serializable_attrs
#serializable_attrs ||= {}
end
And then use that rather than referring directly to the instance variable.

Related

Define method signature for passthrough method defined in super class in Ruby with Sorbet

We're considering adopting Sorbet and I wanted to know if it's possible to define the signature of the following method. This is a common pattern we use: there is an abstract Service class that has a call class method which is responsible for both initializing the class and calling the call instance method on it.
# typed: true
class Service
extend T::Sig
extend T::Helpers
abstract!
def self.call(...)
new(...).call
end
sig{abstract.void}
def call
end
end
class ServiceA < Service
extend T::Sig
extend T::Helpers
sig{params(a: String).void}
def initialize(a:)
#a = a
end
sig{override.void}
def call
puts #a
end
end
ServiceA.call(a: 'some value')
Basically the params for self.call must match the params of the subclass's initializer and its return value must match the subclass's return value for the call instance method. Is there a way to do this with Sorbet?
Here is the error I get.
editor.rb:10: Splats are only supported where the size of the array is known statically https://srb.help/7019
As the sorbet doc already states, splats are not very well supported by Sorbet. However, you can still type-check the base service if you're happy with the constraint that your services will only accept positional arguments. You can do so following this example:
# typed: true
class Service
extend T::Sig
extend T::Helpers
abstract!
def self.call(**kwargs)
new(**kwargs).call
end
def initialize(**kwargs); end
sig { abstract.void }
def call; end
end
class ServiceA < Service
extend T::Sig
extend T::Helpers
sig { override.params(a: String).void }
def initialize(a:)
#a = a
end
sig{ override.void }
def call
puts #a
end
end
ServiceA.call(a: 'some value')

ruby : how can I avoid hardcoding the classes names?

I'm learning Ruby and the difference between class variables & instance variables.
I'm working on a piece of code where I have (a lot of) classes inheriting other classes.
class childImporter < parentImporter
def self.infos
parentImporter.infos.merge({
:name=> 'My Importer',
})
end
def self.schema
schema = parentImporter.schema.deep_merge({
'selectors' => {
'track' => {
'artist'=> {'path'=>{'default'=>'//creator'}},
'title'=> {'path'=>{'default'=>['//name'}},
}
}
})
##schema = schema
end
def initialize(params = {})
super(params,childImporter.schema)
end
end
I have two class variables: infos (importer informations) and schema (json schema).
I need them to be able to get them outside an instance (that is why they are class variables), and to be an extension of their parent value (that is why I deep_merge them), and
My example actually works, but I wonder if there is a way not to hardcode the classes names childImporter and parentImporter and rather use a reference to the parent class, for example having
schema = PARENTCLASS.schema.deep_merge({
instead of
schema = parentImporter.schema.deep_merge({
or
super(params,THISCLASS.schema)
instead of
super(params,childImporter.schema).
Is there a way to achieve this ?
Currently, if I try
super(params,##schema)
I get
NameError: uninitialized class variable ##schema in childImporter
Thanks
I wonder if there is a way not to hardcode the classes names childImporter and parentImporter and rather use a reference to the parent class, for example having
schema = PARENTCLASS.schema.deep_merge({
instead of
schema = parentImporter.schema.deep_merge({
The method you are looking for is superclass – it returns the receiver's parent class. From within a class body or class method, you can call it without an explicit receiver:
class ParentImporter
def self.infos
{ name: 'Parent Importer', type: 'Importer' }
end
end
class ChildImporter < ParentImporter
def self.infos
superclass.infos.merge(name: 'Child Importer')
end
end
ParentImporter.infos #=> {:name=>"Parent Importer", :type=>"Importer"}
ChildImporter.infos #=> {:name=>"Child Importer", :type=>"Importer"}
But there's an even easier way. Classes inherit both, the class methods and the instance methods from their parent class. And in both variants, you can simply call super to invoke the parent's implementation:
class ChildImporter < ParentImporter
def self.infos
super.merge(name: 'Child Importer')
end
end
ParentImporter.infos #=> {:name=>"Parent Importer", :type=>"Importer"}
ChildImporter.infos #=> {:name=>"Child Importer", :type=>"Importer"}
In addition, you might want to memoize the values so they are not re-created every time the methods are called:
class ParentImporter
def self.infos
#infos ||= { name: 'Parent Importer', type: 'Importer' }
end
end
class ChildImporter < ParentImporter
def self.infos
#infos ||= super.merge(name: 'Child Importer')
end
end
Those #infos are so-called class instance variables, i.e. instance variables in the scope of the class object(s). They behave exactly like instance variables in casual instances. In particular, there's no connection between the #infos in ParentImporter and the one in ChildImporter.
or
super(params,THISCLASS.schema)
instead of
super(params,childImporter.schema).
To get an object's class, you can call its class method:
importer = ChildImporter.new
importer.class #=> ChildImporter
importer.class.infos #=> {:name=>"Child Importer", :type=>"Importer"}
The same works from within an instance method:
def initialize(params = {})
super(params, self.class.schema)
end
Note that the class method must always be called with an explicit receiver. Omitting the receiver and just writing class.schema results in an error.
Bottom note: I wouldn't use ## class variables at all. Just call your class methods.
This may help - you can access class and superclass like this:
class Parent
end
class Child < Parent
def self.print_classes
p itself
p superclass
end
end
Child.print_classes
This will print
Child
Parent

My class is calling a non-existing class?

I'm using:
Ruby 1.9.2
Rails 3.1.10
This is my code:
class Report::ExpectedHour
def initialize(user, options = {})
#user = user
#date_start = options[:start]
#date_end = options[:end]
end
def expected_hours_range
previous = ExpectedHour.previous_dates(#user, #date_start).first
hours_range = ExpectedHour.between_dates(#user, #date_start, #date_end)
unless hours_range.include?(previous)
hours_range << previous
end
hours_range
end
end
Every time I call expected_hours_range from my instance I get this error:
NameError: uninitialized constant Report::ExpectedHour::ExpectedHour
from /home/edelpero/.rvm/gems/ruby-1.9.2-p180#titi/gems/aws-s3-0.6.2/lib/aws/s3/extensions.rb:206:in `const_missing_from_s3_library'
from /opt/lampp/htdocs/titi/app/models/report/expected_hour.rb:10:in `expected_hours_range'
I'm not sure why Report::ExpectedHour::ExpectedHour is called because I'm calling ExpectedHour which is an actual existing ActiveRecord class. Also Report::ExpectedHour::ExpectedHour doesn't exist.
When calling classes inside your class methods, ruby expects it to be either a class nested inside you class itself or a constant. Try this:
class MyClass
def some_method
use_external_class = ::ExternalClass::CONSTANTB.bla
# Use the '::'
end
end

What's the difference between self.generate and Invoice.generate?

class Invoice
def Invoice.generate(order_id, charge_amount, credited_amount = 0.0)
Invoice.new(:order_id => order_id, :amount => charge_amount, :invoice_type => PURCHASE, :credited_amount => credited_amount)
end
end
Why would you create Invoice.generate inside Invoice class rather than self.generate?
self.generate is easier to work with, whereas Invoice.generate is arguably more explicit. Other than that, there's no difference between the two.
Explanation
You can define a method on any instance using this form
def receiver.method(args)
end
Check this out
class Foo
end
def Foo.bar
"bar"
end
Foo.bar # => "bar"
And yes, I mean any instance. It's absolutely possible that one instance has some method while another doesn't
f = Foo.new
def f.quux
'quux'
end
f2 = Foo.new
f.quux # => "quux"
f2.quux # => # ~> -:20:in `<main>': undefined method `quux' for #<Foo:0x007fe4e904a6c0> (NoMethodError)
A reminder: inside of class definition (but outside of method definitions) self points to that class.
class Foo
# self is Foo
end
So, armed with this knowledge, the difference between self.generate and Invoice.generate should be obvious.
Under normal circumstances, it would practically have no difference from def self.generate.
The only edge case I can think of is if you have a nested class with the same name, then the explicit version would apply only to the nested class.
class A
def self.x
name
end
def A.y
name
end
class A
# nested class A::A
end
def self.p
name
end
def A.q
name
end
end
> A.x # => "A"
> A.y # => "A"
> A.p # => "A"
> A.q # => NoMethodError: undefined method `q' for A:Class
> A::A.q # => "A::A"
As you see, after a nested class with the same name is defined, subsequent explicit class method definitions made with the class name refer to the nested class, but explicit definitions made beforehand refer to the original.
Implicit definitions made with self always refer to the base class.
You have 2 ways for defining a class method.
1) You can use the name of the class directly
class Test #Test is now an instance of a built-in class named Class
def Test.class_method
"I'm a class method."
end
end
2) You can use the self variable, which is always pointing to the current object
class Test
def self.class_method
"I'm a class method."
end
end
Once you understand that classes are objects, this use of the self variable to define a class method finally makes sense.
The value of self
Not too surprinsingly, when you are inside a class method, the value of self refers to the object that holds the class structure (the instance of class Class). This means that :
class Test
def self.class_method
self.x
end
end
is equivalent to :
class Test
def self.class_method
Test.x
end
end
When you are inside an instance method, the value of self still refers to the current object. This time however, the current object is an instance of class Test, not an instance of class Class.
More info. : http://www.jimmycuadra.com/posts/self-in-ruby

Accessing a Class' instance variable from outside

I understand (I think) the difference between class variables and instance variables of a class in Ruby.
I'm wondering how one can access the instance variables of a class from OUTSIDE that class.
From within (that is, in a class method instead of an instance method), it can be accessed directly, but from outside, is there a way to do MyClass.class.[#$#]variablename?
I don't have any specific reason for doing this, just learning Ruby and wondering if it is possible.
class MyClass
#my_class_instance_var = "foo"
class << self
attr_accessor :my_class_instance_var
end
end
puts MyClass::my_class_instance_var
The foregoing yields:
>> foo
I believe that Arkku demonstrated how to access class variables (##), not class instance variables (#) from outside the class.
I drew the foregoing from this essay: Seeing Metaclasses Clearly
Ruby has Class, Class Object, and Instance.
A Class variable belongs to a Class. A Class instance variable
belongs to a Class Object
Class variable:
Accessible within the class and its instances.
attr_accessor does not work on class variables.
Class instance variable:
Accessible only through the Class.
attr_accessor works if you define it in the class and not in the class object as below.
class A
#b = 1
class << self
attr_accessor :b
end
end
Defining a getter and setter on the instances for the class instance variable b in:
class A
#b = 1
class << self
attr_accessor :b
end
def b
A.b
end
def b=(value)
A.b=value
end
end
Now the class instance variable b can be accessed via the owner Class and its instances.
As a several days old ruby learner, this is the most I can do.
`irb(main):021:0* class A
irb(main):022:1> #b = 1
irb(main):023:1> class << self
irb(main):024:2> attr_accessor :b
irb(main):025:2> end
irb(main):026:1> def b
irb(main):027:2> A.b
irb(main):028:2> end
irb(main):029:1> def b=(v)
irb(main):030:2> A.b=v
irb(main):031:2> end
irb(main):032:1> end
=> :b=
irb(main):033:0> A.b
=> 1
irb(main):034:0> c = A.new
=> #<A:0x00000003054440>
irb(main):035:0> c.b
=> 1
irb(main):036:0> c.b= 50
=> 50
irb(main):037:0> A.b
=> 50
irb(main):038:0>`
Yes, I'm begining to dislike ruby...iso a better solution.
In ruby you can achieve this in 2 ways
manually defining getter and setters
using attr_* methods
Let me elaborate the above ways for you,
Manually defining getter and setters
class Human
def sex=(gender)
#sex = gender
end
def sex
#sex
end
end
//from outside class
human = Human.new
// getter method call
puts human.sex
// setter method call to explicitly set the instance variable
human.sex = 'female'
puts human.sex
// now this prints female which is set
using attr_* methods
class Human
attr_accessor :sex
end
//from outside
human = Human.new
// getter method call
puts human.sex
// setter method call to explicitly set the instance variable
human.sex = 'female'
puts human.sex
// now this prints female which is set
attr_accessor internally creates setter and getter methods for you, if you want only setter you can just use attr_writer and if you want only getter you can use attr_reader.
I hope, I answered your question

Resources