I started with that question
My goal is to try an unusual, but interesting way of calling a block. I came to that code:
class Chips
def self.configure player_class, player_id_method, player_api_method
player_class.send :define_method, player_api_method do
Chips::Player.new(self.send player_id_method)
end
end
def self.fix_game game_id
##game_results = {game_id: game_id, players: []}
yield
puts ##game_results.inspect
end
class Player
def initialize player_id
#player_id= player_id
end
def gain chips
Chips.send :fix_player, #player_id, chips.abs
end
def lose chips
Chips.send :fix_player, #player_id, -chips.abs
end
end
def self.fix_player player_id, chips
##game_results[:players]<< {player_id: player_id, chips: chips}
end
private_class_method :fix_player
end
class User
attr_reader :email
def initialize email
#email = email
end
end
Chips.configure User, :email, :chips
p1= User.new 'david#gmail.com'
p2= User.new 'simon#gmail.com'
# p1.chips and p2.chips should have access to the value
# currently accessible by ##game_results reference
Chips.fix_game 3333 do
p1.chips.gain 300
p2.chips.lose 300
end
... which outputs {:game_id=>3333, :players=>[{:player_id=>"david#gmail.com", :chips=>300}, {:player_id=>"simon#gmail.com", :chips=>-300}]}
I would like to avoid class variable using.
ps
Question creation form hadn't allow me to submit this question until I added more words, which are in fact a pray to The Mighty Doorkeeper. I've brought you more bytes, TMD. I obey.
Related
So when I run my lab i am getting the following error:
Associations — Song and Artist: Artist #add_song adds the song to the current artist's 'songs' collection
Failure/Error: expect(artist.songs).to include(song)
expected ["In the Aeroplane Over the Sea"] to include #<Song:0x0000000001496e88 #name="In the Aeroplane Over the Sea", #artist=#<Artist:0x0000000001496f50 #name="Neutral Milk Hotel", #songs=["In the Aeroplane Over the Sea"]>>
Diff:
## -1,2 +1,2 ##
-[#<Song:0x0000000001496e88 #name="In the Aeroplane Over the Sea", #artist=#<Artist:0x0000000001496f50 #name="Neutral Milk Hotel", #songs=["In the Aeroplane Over the Sea"]>>]
+["In the Aeroplane Over the Sea"]
But when I run my code through pry, I can see the song added into the array of the instance variable, #songs. I am working with two classes, a song class and artist class.
class Artist
attr_accessor :name
attr_reader :songs
##all = []
def initialize(name)
#name = name
save
#songs = []
end
def self.all
##all
end
def save
##all << self
end
def self.destroy_all
##all.clear
end
def self.create(name)
self.new(name)
end
def add_song(song)
if song.artist == nil
song.artist = self
end
#checks if the same song has already been added, otherwise adds new song
if #songs.include?(song.name) == false
#songs << song.name
end
binding.pry
end
end
class Song
attr_accessor :name, :artist
##all =[]
def initialize(name, artist = nil)
#name = name
#artist = artist
save
end
def self. all
##all
end
def save
##all << self
end
def self.destroy_all
##all.clear
end
def self.create(name)
self.new(name)
end
end
Your tests are expecting an aray of Song instances. But what they find is an array of strings.
To fix it you'd just need to change #songs << song.name to #songs << song.
I am sorry if I just gave you the answer instead of hinting you along to solve it yourself, but this isn't really the right platform for that kind of thing. Every question on StackOverflow can be searched by users and we want to keep things helpful to them by getting straight to the point.
However, I can give some advice. Practice reading errors. Try and understand what the error is telling you.
When I run the command I get
all the song names and then the instances following it. How do I only
get the names?
class Song
##all = []
attr_accessor :name
def initialize(name)
#name = name
##all << self
end
def self.all
##all
end
def self.print_all_song_names
##all.each do |song|
puts song.name
end
end
end
hotline_bling = Song.new("Hotline Bling")
thriller = Song.new("Thriller")
ninety_nine_problems = Song.new("99 Problems")
thriller = Song.new("Thriller")
puts Song.print_all_song_names
Which outputs:
Hotline Bling
Thriller
99 Problems
Thriller
#<Song:0x00000000058ced30>
#<Song:0x00000000058cecb8>
#<Song:0x00000000058cebc8>
#<Song:0x00000000058ceb50>
The issue with your code is your calling puts here and there.
The code already calls puts in print_all_song_names, and after you call puts Song.print_all_song_names which roughly means call the method and print the value returned.
each returns a receiver, which means print_all_song_names returns the value of ##all class variable. Which gets printed again.
To fix it, just don’t call puts in the last line; Song.print_all_song_names already prints out everything needed.
Song.print_all_song_names # voilà
Is there any way to pass variables between classes?
I have the next code.
module Test
class Super
def initialize(name)
#name = name
end
end
class Upper
def test
puts #name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()
Thank you!
No, because a is an instance of the class. Two answers for you;
1) It's better programming practice to have send a to b. So you'd do something like this; (assuming attr_reader :name)
class Upper
def test(s)
s.name
end
end
a = Test::Super.new('My Name')
u = Test::Upper.new
u.test(a)
or you could have it part of the setup; I won't give you all the code, but here's how it'd look
a = Test::Super.new('My name')
b = Test::Upper.new(a)
b.test
=> 'My name'
Neither of these examples is particularly good practice for classes but I imagine you have a more specific use case you're trying to achieve that has been anonymised for the purpose of this question :)
If for some reason instances of the class Upper need to have an access to the internals of instances of the class Super, it means you have a design flaw.
One possible way would be Super needs to expose the variable via a getter:
module Test
class Super
def initialize(name)
#name = name
end
def name
#name
end
end
end
Now you might get the name with Test::Super.new("my name").name.
Another possibility is Upper is actually a subclass of Super:
class Upper < Super
def test
puts #name
end
end
Now Test::Upper.new("my name").test will print "my name", because Upper derives the implementation from Super.
Also, one might use an instance variable on the enclosing module level:
module Test
def self.name=(name)
#name = name
end
def self.name
#name
end
class Super
def initialize(name)
Test.name = name
end
end
class Upper
def test
puts Test.name
end
end
end
This would print:
▶ Test::Super.new("my")
#⇒ #<Test::Super:0x0055dae57fe390>
▶ Test::Upper.new.test
#⇒ "my"
You can make use of class variables in module, which is supported natively by Ruby.
You can do:
module Test
##name = ''
class Super
def initialize(name)
##name = name
end
end
class Upper
def test
puts ##name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()
I am studying the adapter pattern implementation in ruby. I want to access an instance variable within the adapter module definition. Take a look at the following code:
module Adapter
module Dog
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: woof!"
end
end
module Cat
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: meow!"
end
end
end
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
def speak
self.adapter.speak
end
def adapter
return #adapter if #adapter
self.adapter = :dog
#adapter
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
To test it out I did the following:
animal = Animal.new("catdog")
animal.adapter = :cat
animal.speak
I want it to return the following:
catdog says: meow!
Instead it says:
Adapter::Cat says: meow!
Any tips on how I can get access to the Animal#name instance method from the adapter module? I think the issue is that my adapter methods are class-level methods.
Thanks!
You need to use your Module as a mixin and provide a way to keep track of which module is active, the methods don't seem to be overwritten by reincluding or reextending so I took the extend and remove methods I found here.
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
module Cat
def speak
puts "#{name} says: meow!"
end
end
def extend mod
#ancestors ||= {}
return if #ancestors[mod]
mod_clone = mod.clone
#ancestors[mod] = mod_clone
super mod_clone
end
def remove mod
mod_clone = #ancestors[mod]
mod_clone.instance_methods.each {|m| mod_clone.module_eval {remove_method m } }
#ancestors[mod] = nil
end
end
class Animal
include Adapter
attr_accessor :name, :adapter
def initialize(name)
#name = name
#adapter = Adapter::Dog
extend Adapter::Dog
end
def adapter=(adapter)
remove #adapter
extend Adapter::const_get(adapter.capitalize)
#adapter = Adapter.const_get(adapter.capitalize)
end
end
animal = Animal.new("catdog")
animal.speak # catdog says: woof!
animal.adapter = :cat
animal.speak # catdog says: meow!
animal.adapter = :dog
animal.speak # catdog says: woof!
This is because name inside of the module context refers to something entirely different than the name you're expecting. The Animal class and the Cat module do not share data, they have no relationship. Coincidentally you're calling Module#name which happens to return Adapter::Cat as that's the name of the module.
In order to get around this you need to do one of two things. Either make your module a mix-in (remove self, then include it as necessary) or share the necessary data by passing it in as an argument to speak.
The first method looks like this:
module Adapter
module Dog
def self.speak(name)
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def speak
self.adapter.speak(#name)
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
That doesn't seem as simple as it could be as they basically live in two different worlds. A more Ruby-esque way is this:
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
extend(#adapter)
end
end
I have the 3 following classes:
Base
module TitleSource
class Base
include Comparable
attr_accessor :company
attr_accessor :priority
attr_accessor :target_title
def initialize(args={})
self.company = args[:company]
self.priority = args[:priority]
self.target_title = args[:target_title]
end
def create_contact_source
raise NotImplementedError
end
def <=>(other)
fetch_value <=> other.fetch_value
end
protected def fetch_value
value
end
private def value
raise NotImplementedError
end
end
end
UnmatchedTitle
module TitleSource
class UnmatchedTitle < Base
def create_contact_source
::ContractorUi::ContactSource.generate_from_unmatched_title(self)
end
private def value
100
end
end
end
IncompleteContact
module TitleSource
class IncompleteContact < Base
attr_accessor :target_title_name
attr_accessor :contact
def initialize(args={})
super
self.target_title_name = args[:target_title_name]
self.contact = args[:contact]
end
def create_contact_source
::ContractorUi::ContactSource.generate_from_incomplete_contact(self)
end
private def value
10
end
end
end
I'm currently reading POODR and came up with this design, which so far served me well.
However, for didactical reasons, I would like to know how I can remove the dependency on ::ContractorUi::ContactSource if it's worth it and if it should be done.
I don't like the idea of passing it in with the constructor because the entire purpose of TitleSource module is to actually generate a ContactSource, but would like to hear from more experienced people. Reading the book (and some on-the-field experience) allowed me to understand how much decoupling is important
I can confirm that having a single dependency is not a big issue in this case. Based on POODR, this code can react to changes well enough, so keeping the class dependency in this case is fine, expecially considering it's the main purpose of the entire class, to generate an instance of ContactSource