Reputation: 301
I have a system in which the user can create a new object of the class Map that has a subclass of SubMap. The thing is, there can be multiple Map objects, and for each one of those, there can be multiple SubMap objects. I need to be able to create a SubMap object that can both get and change the Map object's @b value of the unique ID passed during the SubMap instantiation. Ex:
map1=Map.new(10,5) #Map is assigned unique ID of 1 and has a few instance variables (@b = 5)
point=Map::SubMap.new(1) #Assigned to the map object with an ID of 1, which is map1
point.change_b(8) #Change map1's @b value to 8.
Here is an expanded code example:
class Map
@@id=1
def change_b(b)
@b=b
end
def initialize(a,b)
@id=@@id
@@id+=1
@a=a
@b=b
@map="#{a}_#{b}"
end
end
class SubMap < Map
def initialize(mapId)
@mapID=mapId
end
def change_b(b)
super
end
end
map=Map.new(5,2) #Create New Map ID: 1, @b = 2
second_map=Map.new(6,1) #Create Test Map ID: 2, @b = 1
point=Map::SubMap.new(1) #Just affect the map with the ID of 1
point.change_b(5) #Change map's (Instance of Map with ID of 1) instance variable of @b to 5
##second_map's @b value is unchanged.
I'll welcome any other methods for doing this (no pun intended), Thanks in advance. Also, I apologize for any formatting errors (Missing indents and such that I may have missed), lets just say me and the SO code format didn't agree on a few things.
Upvotes: 1
Views: 411
Reputation: 110755
I suggest doing that as follows.
class Map
@id=1
singleton_class.send(:attr_accessor, :id)
@id_to_instance = {}
singleton_class.send(:attr_accessor, :id_to_instance)
attr_reader :b
def initialize(a,b)
self.class.id_to_instance[self.class.id] = self
self.class.id += 1
@a=a
@b=b
end
def change_b(b)
@b=b
end
end
class SubMap < Map
attr_reader :mapID
def initialize(mapID)
@mapID=mapID
end
def change_b(b)
self.class.superclass.id_to_instance[@mapID].change_b b
end
end
map = Map.new(5,2)
second_map = Map.new(6,1)
point = SubMap.new(1)
point.change_b(5)
Confirm the value of the correct instance variable @b
was changed
Map.id_to_instance[point.mapID].b
#=> 5
@id
(in Map
) rather than a class variable @@id
because the former is shielded from subclasses, which is good practice.@id_to_instance
(in Map
) that maps values of the counter @id
to the associated instance of Map
.@id
(in Map
), so that instances of Map
can read and increment the instance variable's value.@id_to_instance
(in Map
), so that instances of Map
can set its value and instances of SubMap
can read its value.@b
(for instances of Map
) for use by SubMap.change_b
.Map#initialize
adds a key-value pair to the hash @id_to_instance
that maps the current value of the class instance variable @id
to the instance of Map
being created, and increments the counter @id
.SubMap#change
obtains the value of Map
's instance variable id_to_instance
for the key equal to the value of the SubMap
instance's instance variable @mapID
and invokes the instance method :change_b
, with argument b
, on that instance of Map
.Upvotes: 1
Reputation: 850
Both answers are true, but old objects will garbage collected and will be lost. I think you should hold the created objects in a class array.
class Map
@@maps = []
def change_b(b)
@b=b
end
def initialize(a,b)
@id=@@maps.size + 1
@a=a
@b=b
@map="#{a}_#{b}"
@@maps << self
end
def self.find_by_id(id)
@@maps.select {|m| m.instance_variable_get(:@id) == id}.first
end
end
class SubMap < Map
def initialize(mapId)
@map = Map.find_by_id(mapId)
end
def change_b(b)
@map.change_b(b)
end
end
Map.new(5,2) # => #<Map:0x007ff94a822800 @id=1, @a=5, @b=2, @map="5_2">
Map.new(6,1) # => #<Map:0x007ff94a821fb8 @id=2, @a=6, @b=1, @map="6_1">
point=SubMap.new(1) # => #<SubMap:0x007ff94a820938 @map=#<Map:0x007ff94a822800 @id=1, @a=5, @b=2, @map="5_2">>
point.change_b(5) # => 5
Also you can instantiate SubMap directly, no need Map::SubMap.new(1)
.
Upvotes: 1
Reputation: 12256
An alternative way to do that, use your instance of map as a factory to create submaps (no complicated code here):
class Map
@@id=1
def change_b(b)
@b=b
end
def initialize(a,b)
@id=@@id
@@id+=1
@a=a
@b=b
@map="#{a}_#{b}"
end
def sub_map
return SubMap.new(self)
end
def to_s
"#{@a}_#{@b}"
end
end
class SubMap
def initialize(map)
@map=map
end
def change_b(b)
@map.change_b(b)
end
end
map=Map.new(5,2) #Create New Map ID: 1, @b = 2
second_map=Map.new(6,1) #Create Test Map ID: 2, @b = 1
puts second_map
# 6_1
puts map
# 5_2
point = second_map.sub_map
point.change_b(5)
puts second_map
# 6_5
Upvotes: 0
Reputation: 1854
My (probably highly suboptimal) solution is to get all instances of Map, find the one who has an @id equal to the submap's @mapID. Once we have it, we set it's @b to b.
So it works as follows :
ObjectSpace.each_object(Map)
It gets an enumarator of all instances of Map
, you can find more infos here : How do I list all objects created from a class in Ruby?
Enumerator.select {|m| m.instance_variable_get(:@id) == @mapID}
From the Enumerator we got in step 1, we select only the instances where the instance variable @id is equal to the current subMap's @mapID.
MapInstance.instance_variable_set(:@b, b)
Now that we have the map whose @id is equal to the submap's @mapID, then we just have to set this map's instance variable @b to the parameter b of the method. See here for more infos : How to set private instance variable used within a method test?
Putting it all together we obtain :
class Map
@@id=1
def change_b(b)
@b=b
end
def initialize(a,b)
@id=@@id
@@id+=1
@a=a
@b=b
@map="#{a}_#{b}"
end
end
class SubMap < Map
def initialize(mapId)
@mapID=mapId
end
def change_b(b)
#https://stackoverflow.com/questions/14318079/how-do-i-list-all-objects-created-from-a-class-in-ruby
#https://stackoverflow.com/questions/9038483/how-to-set-private-instance-variable-used-within-a-method-test
ObjectSpace.each_object(Map).select {|m| m.instance_variable_get(:@id) == @mapID}.instance_variable_set(:@b, b)
end
end
map=Map.new(5,2) #Create New Map ID: 1, @b = 2
second_map=Map.new(6,1) #Create Test Map ID: 2, @b = 1
point=Map::SubMap.new(1) #Just affect the map with the ID of 1
point.change_b(5) #Change map's (Instance of Map with ID of 1) instance variable of @b to 5
p map
#<Map:0x00000002276f80 @id=1, @a=5, @b=5, @map="5_2">
Upvotes: 0