I have TONS of instance methods, all sharing the same instance variables.
Since the class became huge, I split the methods into around 50 modules.
And then the class that is left is including all those 50 modules.
I ended up with an unbelievable ugly code which is full of instance methods like "module_name_method_name" to avoid collisions of method names.
The problem is that each Module might have similar (similar, not same) functionality (in turn similar method names).
My current code looks like this:
The modules:
module Toyota
def toyota_method1;#foo...;end
def toyota_method2;#foo...;end
....
end
module Ford
def ford_method1;#foo...;end
def ford_method2;#foo...;end
....
end
.... around 50 such modules
class Cars
include Toyota
include Ford
include ...
def foo
#foo = "bar"
#bar = "foo"
...
toyota_method1
ford_method2
toyota_method2
...
end
end
How could I design my code better?
The most important thing is that ALL instance methods need to share the same instance variables.. or at least somehow have access to the same data!
EDIT:
I just came up with this myself:
class Toyota
attr_accessor :foo
def method1
puts #foo
end
end
class Ford
attr_accessor :foo
def method1
puts #foo
end
end
class Cars
def foo
#foo = "bar"
toyota = Toyota.new
toyota.foo = #foo
toyota.method1
ford = Ford.new
ford.foo = #foo
ford.method1
end
end
cars = Cars.new
cars.foo
In fact it solves the ugly method name problem, but now I'm dealing with new problems: the variable #foo might be quite large and it's duplicating itself 50 times (or more) into memory (since I have 50 such classes).
Any other solutions?
It's not only duplicating 50 times or more, its duplicated per instance since it's an instance variable. From what I can tell about your code, and you have to correct me if I'm wrong, it would be best for you to use inheritance along with variable sharing on the class level. This may not work if your car class really needs to know all the different methods from all kind of models it might be, but then I would say you've done something inherently wrong from the beginning. So let's look at inheritance:
class Car
attr_accessor :foo
# lets you initialize attributes via a hash
#
def initialize attrs={}
super()
attrs.each do |att, value|
self.send(:"#{att}=", value)
end
end
end
Inherit Toyota from Car:
class Toyota < Car
def method1
# do what you want to do in this method
end
end
Same for Ford:
class Ford < Car
def method1
# do what you want to do in this method
end
end
Like this you won't have to put namespaces in front of your method:
Ford.new(:foo => 'fordfoo').foo #=> will put out «fordfoo»
Toyota.new(:foo => 'toyotafoo').foo #=> will put out «toyotafoo»
Sharing across classes
Now, if foo is Shared across all cars, you can make it either a) a constant on the car class if it is static:
class Car
FOO = 'bar'
def put_foo
puts FOO
end
end
Like this, all Instances of Car, Toyota or Ford will have access to the static constant FOO, which will only exist one time in memory:
Toyota.new.put_foo #=> 'bar'
If foo has to be assignable and only has to exist one time for the whole inheritance tree, use class variables:
class Car
class << self
def foo= data
##foo = data
end
end
def foo= data
self.class.foo = data
end
def foo
##foo
end
end
Now ##foo only exists once for the inheritance tree, so if you do this:
car = Car.new
toyota = Toyota.new
toyota.foo = 'toyota'
car.foo #=> 'toyota'
This can lead to serious problems if you're not paying attention, but if foo must be the same anyway for all classes, it's the way to go. There are also some issues with Thread Safety you will have to address (with Mutex) working with class variables.
If Car, Toyota and Ford all have different foo, but the instances of each class need to share the same foo (so it is 3 foo's in total), use class instance variables:
class Car
class << self
def foo= data
#foo = data
end
def foo
#foo
end
end
def foo= data
self.class.foo = data
end
def foo
self.class.foo
end
end
Like this you will get:
car = Car.new
car.foo = 'bla'
toyota = Toyota.new
toyota.foo #=> is nil
toyota.foo = 'bar'
car.foo #=> still 'bla'
toyota.foo #=> is 'bar'
These are the ways to share the same data across instances.
Related
I am trying to update the parent class variable from the child class. This is what I have
class Dog
def initialize
#breed = []
end
def show
puts "#{#first_name} of breed #{#breed}" ### <- Changed
end
end
class Lab < Dog
attr_reader :first_name, :i
def initialize
super ### <- Added
#first_name = "good dog"
#i = 1
end
def add
#breed << #i
#i += 1
end
end
As you can see, I have a #breed variable in the parent class, which the child class has access to. But when I update the #breed variable from the child class and call the same variable from the parent class it does not show the update values. How can I do that?
irb(main):172:0> d = Dog.new
=> #<Dog:0x00005607908130d8 #breed=[]>
irb(main):173:0> l = Lab.new
=> #<Lab:0x000056079081add8 #breed=[], #first_name="good dog", #i=1>
irb(main):174:0> d.show
of breed []
irb(main):175:0> l.show
good dog of breed []
irb(main):176:0> l.add
=> 2
irb(main):177:0> l.show # inherited variable got updated
good dog of breed [1]
irb(main):178:0> d.show # parent array did not get updated?
of breed []
irb(main):179:0>
Calling d.show returns an empty array, but calling l.show returns the filled array. I would like my parent class also return the filled array. Is this possible? What am I missing.
d and l in your example are absolutely different objects, having nothing in common. That’s not how inheritance works.
There would be a brief example of using the instance variable from parent class.
class Dog
def initialize
#breed = []
end
def add
puts "Dog: #{#breed}"
end
end
class Lab < Dog
def initialize
super ### <- #breed is now declared
end
def add
puts "Lab 1: #{#breed}"
#breed << :ok
super
puts "Lab 2: #{#breed}"
end
end
Lab.new.add
#⇒ Lab 1: []
#⇒ Dog: [:ok]
#⇒ Lab 2: [:ok]
No, this is not possible, at least not without some (unadvisable) hacking.
You are missing the distinction between classes and instances. There can be a hierarchy of classes, as you created with Dog and Lab, but the instances you create from these classes don't have a hierarchical relationship. The (instance) variables, like #breed, are private to each instance and each instance has its own copies.
In other words: There is no "parent array", only an array which has been declared in a parent class.
It looks like you want a collection of Dog instances. The right place would a variable outside of the Dog class.
class Dog
attr_reader :first_name
def initialize(name)
#first_name = name
end
def show
puts "#{first_name} of breed #{self.class.name}"
end
end
class Lab < Dog
def initialize
super('good dog')
end
end
class Schnauzer < Dog
end
class BlackLab < Lab
end
Notice that I moved first_name from Lab to Dog, because Dog#show already uses it and it makes no sense to declare it in a sub class.
You can use this class hierarchy to collect multiple instances in an array which is stored outside of the class hierarchy:
breeds = []
breeds << Dog.new('some dog')
breeds << Lab.new
breeds << Dog.new('another dog')
breeds << Schnauzer.new('yet another')
breeds << BlackLab.new
breeds.each do |dog_instance|
dog_instance.show
end
This produces the following output:
some dog of breed Dog
good dog of breed Lab
another dog of breed Dog
yet another of breed Schnauzer
good dog of breed BlackLab
class Dog
def initialize
puts "self in Dog#initialize = #{self}"
#breed = []
end
def show
puts "#{#first_name} of breed #{#breed}"
end
end
class Lab < Dog
attr_reader :first_name, :i
def initialize
super
#first_name = "good dog"
#i = 1
end
def add
#breed << #i
#i += 1
end
end
Let's create an instance of Lab and examine its instance variables:
sue = Lab.new
#=> #<Lab:0x000059195710eec8 #breed=[], #first_name="good dog", #i=1>
# displays: self in Dog#initialize = #<Lab:0x000059195710eec8>
sue.instance_variables
#=> [:#breed, :#first_name, :#i]
sue.first_name"
#=> "good dog"
sue.i
#=> 1
sue.instance_variable_get(:#breed)
#=> []
Notice I had to use Object#instance_variable_get to obtain the value of sue's instance variable #breed because no getter had been created for that instance variable.
You say you wish to "update the parent class variable from the child class". Firstly the parent class, Dog has no class variables or instance variables (sometimes referred to as class instance variables). I assume you mean the instance variable #breed that is associated with each instance of Dog. In fact, it appears that one would not likely create an instance of Dog; that Dog has been created only to define methods and instance variables that are to be used by subclasses of Dog. Nevertheless, let's create an instance of Dog:
bob = Dog.new
#=> #<Dog:0x0000591956ffd4a8 #breed=[]>
# displays: self in Dog#initialize = #<Dog:0x0000591956ffd4a8>
bob.instance_variables
#=> [:#breed]
bob.instance_variable_get(:#breed)
#=> []
Let's also change the value of #breed for this instance:
bob.instance_variable_get(:#breed) << "pug"
#=> ["pug"]
Now let's add a method get_iv to Lab to get the value of the instance variable #breed of an instance of Dog. Think about the information that must be passed to this method. It needs to know which instance of Dog we are concerned with (as different instances may very well have different values for their instance variables), and for that instance, which instance variable we are interested in. (Here there is but one, but let's make it general so that we could add instance variables to Dog's instances.)
class Lab
def get_iv(instance, instance_variable)
instance.public_send(:instance_variable_get, instance_variable)
end
end
See Object#public_send. So we could now write:
sue.get_iv(bob, :#breed)
#=> ["pug"]
sue.get_iv(bob, :#breed) << "collie"
#=> ["pug", "collie"]
Sure enough, we have accomplished the task:
bob.instance_variable_get(:#breed)
#=> ["pug", "collie"]
But wait! Now let's create a completely unrelated class and an instance of it.
class Bird
def initialize
#type = ["canary"]
end
end
tweetie = Bird.new
#=> #<Bird:0x0000591956ffee98 #type=["canary"]>
Next, let sue get and alter the value of tweetie's instance variable #type:
sue.get_iv(tweetie, :#type)
#=> ["canary"]
sue.get_iv(tweetie, :#type) << "robin"
#=> ["canary", "robin"]
and confirm they've been changed:
tweetie.instance_variable_get(:#breed)
#=> ["canary", "robin"]
The point is that the instance method we must add to Lab requires two pieces of information:
the instance whose instance variable's value is to be obtained; and
the name of the instance variable.
This has nothing to do with the fact that Lab is a subclass of Dog! Therefore, to get or set the value of an instance variable of an instance of Lab's superclass is no different than getting or setting the value of an instance variable of any other class.
I'm currently doing some metaprogramming with ruby, and I'm trying to isolate the methods of class (that class is in another file, that I get by a require). I can get all the methods, thanks to klass.public_instance_methods(false), but I in the sametime, the array given also have all the attributes of the class. How could I isolate them ? In others related questions on SO, they suggest to use klass.instance_variables but when I do that, it only returns an empty array.
I can't seem to wrap my head around that one. I don't understand why there isn't a method specifically for that already...
For example:
I have in a file this class :
class T
attr_reader:a
def initialize(a)
#a = a
end
def meth
#code here
end
end
And, in another file, i have
require_relative 'T.rb'
class meta
def initialize
methods = T.public_instance_methods(false) #=> here methods = [:a,:meth] but I would want only to have [:meth]
#rest of code
end
end
For class defined like this:
class Klass
attr_accessor :variable
def initialize(variable)
#variable = variable
end
def method
end
end
you can find public non-attr instance methods using public_instance_methods and instance_variables methods.
public_instance_methods = Klass.public_instance_methods(false)
# [:method, :variable, :variable=]
instance_variables = Klass.new(nil).instance_variables
# [:#variable]
getters_and_setters = instance_variables
.map(&:to_s)
.map{|v| v[1..-1] }
.flat_map {|v| [v, v + '=']}
.map(&:to_sym)
# [:variable, :variable=]
without_attr = public_instance_methods - getters_and_setters
# [:method]
This is impossible. Ruby's "attributes" are completely normal methods. There is no way to distinguish them from other methods. For example, these two classes are completely indistinguishable:
class Foo
attr_reader :bar
end
class Foo
def bar
#bar
end
end
You can try to be clever and filter them out based on instance variables, but that is dangerous:
class Foo
# can filter this out using #bar
attr_writer :bar
def initialize
#bar = []
end
end
class Foo
def initialize
#bar = []
end
# this looks the same as above, but isn't a normal attribute!
def bar= x
#bar = x.to_a
end
end
In C programming I believe they call this pass by reference. What I want to do is this.
class A
attr_accessor :foo
def initialize
#foo = 'foo'
end
def get_b
_b = Object.new
_b.extend B
_b.foo = #foo
end
module B
attr_accessor :foo
def change_foo
#foo = 'bar'
end
end
end
a = A.new
puts a.foo # 'foo'
b = a.get_b
puts b.foo # 'foo'
b.change_foo
puts b.foo # 'bar'
puts a.foo # This should also be 'bar' but is instead still 'foo'
After b.change_foo I would like the value of a.foo to be modified. Is there a way of passing the reference of #foo from class A to module B instead of the value?
With this concrete example of strings, you can make it work.
module B
attr_accessor :foo
def change_foo
# #foo = 'bar' # this won't work
foo.replace('bar')
end
end
When you do a #foo = 'bar', you're completely breaking any connection between foo of B and foo of A. They're now two separate objects.
What the code above does is, instead of making another object, use the reference to the object to call a method on it (which will change its state). Will work equally well with other mutable objects (arrays, hashes, instances of your custom classes). But not with immutable primitives like integers.
Is there a way of passing the reference of #foo from class A to module B instead of the value?
A reference (or a "pointer", in C-speak) is actually passed here. You then overwrite it.
Objects are passed as reference in Ruby, but not pointers to variables. This means that, although you can modify any object, if you change the variable's reference, this change will occur only in the current scope. I'd recommend you to actually change your architecture and use more object-oriented techniques, instead of trying to rely on error-prone language features like this. For example, you can use composition and delegation to implement what you're trying to accomplish:
class A
attr_accessor :foo
def initialize
#foo = 'foo'
end
def get_b
_b = Object.new
_b.extend B
_b.a = self
end
module B
attr_accessor :a
def foo
a.foo
end
def foo=(value)
a.foo = value
end
def change_foo
a.foo = 'bar'
end
end
end
I don't know exactly your purpose, but I'd probably not dynamically extend modules in a factory method like this. I prefer to create classes whose purpose is clear, without depending on context.
I have a base class A containing a public method which works with an array provided by descendants of A. The array is:
used only by the methods defined in the base class A
constant, but there are special cases in which it varies.
How should I approach the object design of the problem thus defined? I am still not sure myself where to store that array, whether in a constant, an instance variable or an instance method. Please show me how to do this.
Last time I was evil to a female newbie. This time over, I'll try to be nice.
Way one, using a constant:
A = Class.new
class B < A
FOO = [ :hello, :world ] # this is your array
end
# Access different constants defined in different descendants is tricky, like this:
class A
def foo_user
puts self.class.const_get :FOO
end
end
B.new.foo_user #=> [ :hello, :world ]
# Note theat you can't do this:
class A
def foo_user
puts FOO # this would look for FOO in A mother class only
end
end
B.new.foo_user #=> error
Way two, using an instance variable belonging to the subclasses of A:
A = Class.new
class B < A
#foo = [ "hello", "world" ]
end
# This way is more usable. I would go about it like this:
class A
class << self
attr_reader :foo # defining a reader class method
end
def foo
self.class.foo # delegating foo calls on the instances to the class
end
def foo_user
puts foo
end
end
B.new.foo_user #=> [ :hello, :world ]
Way three, using an instance method defined on the descendants:
A = Class.new
class B < A
def foo
[ "hello", "world" ]
end
end
# This way is also usable.
class A
def foo_user
puts foo
end
end
The choice between the way 2 (instance variable belonging to a descendant class) and 3 (method defined on a descendant class) depends on how flexible the value (the array) needs to be. Way 2 is most flexible, but way 3 takes less code.
For example:
class Animal
def make_noise
print NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # I want this to print "bark"
How do I accomplish the above? Currently it says
uninitialized constant Animal::NOISE
I think that you don't really want a constant; I think that you want an instance variable on the class:
class Animal
#noise = "whaargarble"
class << self
attr_accessor :noise
end
def make_noise
puts self.class.noise
end
end
class Dog < Animal
#noise = "bark"
end
a = Animal.new
d = Dog.new
a.make_noise #=> "whaargarble"
d.make_noise #=> "bark"
Dog.noise = "WOOF"
d.make_noise #=> "WOOF"
a.make_noise #=> "whaargarble"
However, if you are sure that you want a constant:
class Animal
def make_noise
puts self.class::NOISE
# or self.class.const_get(:NOISE)
end
end
one way to do it without class instance variables:
class Animal
def make_noise
print self.class::NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # prints bark
I think you have the wrong concept here. Classes in Ruby are similar to classes in Java, Smalltalk, C#, ... and all are templates for their instances. So the class defines the structure and the behavior if its instances, and the parts of the structure and behavior of the instances of its subclasses but not vice versae.
So direct access from a superclass to a constant in a subclass is not possible at all, and that is a good thing. See below how to fix it. For your classes defined, the following things are true:
class Animal defines the method make_noise.
instances of class Animal may call the method make_noise.
class Dogdefines the constant NOISE with its value.
instances of Dog and the class Dog itself may use the constant NOISE.
What is not possible:
Instances of Animal or the class Animal itself have access to constants of the class Dog.
You may fix that by the following change:
class Animal
def make_noise
print Dog::NOISE
end
end
But this is bad style, because now, your superclass (which is an abstraction about Dog and other animals) knows now something that belongs to Dog.
A better solution would be:
Define an abstract method in class Animal which defines that make_noise should be defined. See the answer https://stackoverflow.com/a/6792499/41540.
Define in your concrete classes the method again, but now with the reference to the constant.
If you're doing this to configure your sub classes so that the base class has access to the constants then you can create a DSL for them like this:
module KlassConfig
def attr_config(attribute)
define_singleton_method(attribute) do |*args|
method_name = "config_#{attribute}"
define_singleton_method method_name do
args.first
end
define_method method_name do
args.first
end
end
end
end
class Animal
extend KlassConfig
attr_config :noise
def make_noise
puts config_noise
end
end
class Dog < Animal
noise 'bark'
end
This way is a bit more performant as on each method call you don't have to introspect the class to reach back (or is it forward?) for the constant.
If you want it the Object-Oriented Way (TM), then I guess you want:
class Animal
# abstract animals cannot make a noise
end
class Dog < Animal
def make_noise
print "bark"
end
end
class Cat < Animal
def make_noise
print "meow"
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow
If you want to refactor to prevent duplicating the code for print:
class Animal
def make_noise
print noise
end
end
class Dog < Animal
def noise
"bark"
end
end
class Cat < Animal
def noise
if friendly
"meow"
else
"hiss"
end
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow or hiss