I am new to Ruby and have been following a couple of tutorials to learn more about it.
Here's the baseline code. I have a Publisher and a class, Checkout, that includes this Publisher.
module MyPublisher
def subscribe(subscribers)
#subscribers += subscribers
end
def publish(event, *payload)
#subscribers ||= []
#subscribers.each do |subscriber|
subscriber.public_send(event.to_sym, *payload) if subscriber.respond_to?(event)
end
end
end
class Checkout
include MyPublisher
end
checkout = Checkout.new
items = []
checkout.subscribe "event-item-added" do |data|
# How can I get here inside this listener? So that "item << data" is executed?
puts "checkout received an item!"
items << data
end
queue.publish "event-item-added", 52
queue.publish "event-item-added", 90
queue.publish "event-item-added", 16
My question is: how to get the data that is published inside the ...do |data| block/listener? (I also put a comment to show where I am talking about).
Related
I am trying my hands at Ruby, below is the code that I am writing in 2 different ways to understand Ruby Classes. In first block I am using accessor method (combination of accessor read & write) and I want to print final line as "lord of the rings is written by Tolkien and has 400 pages". How can I make that happen? I understand that adding string and integer will throw an error. I can get them to print on separate lines, its just that I can't get them in a sentence.
class Book
attr_accessor :title, :author, :pages
end
book1 = Book.new()
book1.title = 'lord of the rings'
book1.author = 'Tolkien'
book1.pages = 400
puts book1.title
puts book1.author
puts book1.pages
#puts book1.title + " is written by " + book1.author + " and has " + book1.pages + " pages" <<<this errors out for known reason>>>
Second piece of code doing the same thing but I am using instance variable and have figured out how to get desired output. However, please advise if there's a better way of doing this?
class Novel
def initialize(title, author, pages)
#title = title
#author = author
#pages = pages
end
def inspect
"#{#title} is written by #{#author} and has #{#pages} pages"
end
end
novel1 = Novel.new('harry potter', 'JK Rowling', 300)
puts novel1.inspect
In your first example you are providing access the info you want and leaving it up to the client to format the output. For example you could have gotten what you wanted by adding this line in place of your commented line.
puts "#{book1.title} is written by #{book1.author} and has #{book1.pages} pages"
In your second example you are "pushing" that code down into the Novel class and proving a method to produce the output you want. BTW, don't use inspect as a method name, inspect is already a defined method
For example the following will print the same info twice.
class Novel
attr_accessor :title, :author, :pages
def initialize(title, author, pages)
#title = title
#author = author
#pages = pages
end
def info
"#{#title} is written by #{#author} and has #{#pages} pages"
end
end
novel = Novel.new('harry potter', 'JK Rowling', 300)
puts novel.info
puts "#{novel.title} is written by #{novel.author} and has #{novel.pages} pages"
Hello I have modified this older code to scrape twitter usernames, but for some reason it also scrapes user ids. I dont understand how it does that, since I dont see anywhere in the code "user_id" which you should use to get user ids according to twitter api documentation.
Here is the code
def my_usernames
"UHDTelevisions"
end
def my_userinfo(names)
#client.followers(names)
end
def my_userhash(users)
userhash = {}
users.each do |user|
userhash[user.screen_name] = user.id.to_s
end
return userhash
end
def my_users
my_userhash(my_userinfo(my_usernames))
end
def my_csv(my_users)
CSV.open('./my_users.csv','a+') do |csv|
my_users.each do |k,v|
csv << [k,v]
end
end
Here is the line that builds a hash {name ⇒ id}:
userhash[user.screen_name] = user.id.to_s
Here we already got the user object, that contains id amongst other user params. To return the list of names, one might simply:
#client.followers("UHDTelevisions").map &:screen_name
instead of all the code above.
If you wanted to keep a parallel structure you could change it to have my_userarray (since you only need values, not key-value pairs, I assume)
def my_userarray(users)
userarray = []
users.each do |user|
userarray << user.screen_name
end
return userarray
end
You would need to update the my_users method as well, of course, to reflect the new method name for my_userarray
I am doing a task that requires me add some products together and give a 10% discount providing the total is above £60. I have done the following:
class Checkout
def initialize (rules)
#rules = rules
#cart = []
end
def scan (item)
if product == Product.find(item)
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
def total
#cart = #rules.apply #cart
end
def self.find item
[item]
end
co = Checkout.new(Promotional_Rules.new)
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(3)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(3)
co.scan(1)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(1)
co.scan(3)
puts "Total price: #{co.total}"
puts
However when I run this in irb I get undefined variable or method product. Sounds a bit daft but this should work.
You're using one too many equal signs
def scan (item)
# if product == Product.find(item)
if product = Product.find(item) # <--- should be this
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
Of course, then you'll get a different error since find doesn't exist on Product yet... which I think you're trying to define here:
def self.find item # self should be changed to Product
[item]
end
Then you're going to get an error for apply not existing for Promotional_Rules ...
One of the best ways to debug these errors is follow the stack traces. So for the last error I get the following message:
test.rb:53:in `total': undefined method `apply' for #<Promotional_Rules:0x007f94f48bc7a8> (NoMethodError)
from test.rb:72:in `<main>'
That's basically saying that at line 53 you'll find apply hasn't been defined for #rules which is an instance of Promotional_Rules. Looking at the Promotional_Rules class you've clearly defined that method as apply_to_item and not apply. If you keep following and fixing the rabbit trails like this for stack traces you'll be able to debug your program with ease!
I am running rspec tests on a catalog object from within a Ruby app, using Rspec::Core::Runner::run:
File.open('/tmp/catalog', 'w') do |out|
YAML.dump(catalog, out)
end
...
unless RSpec::Core::Runner::run(spec_dirs, $stderr, out) == 0
raise Puppet::Error, "Unit tests failed:\n#{out.string}"
end
(The full code can be found at https://github.com/camptocamp/puppet-spec/blob/master/lib/puppet/indirector/catalog/rest_spec.rb)
In order to pass the object I want to test, I dump it as YAML to a file (currently /tmp/catalog) and load it as subject in my tests:
describe 'notrun' do
subject { YAML.load_file('/tmp/catalog') }
it { should contain_package('ppet') }
end
Is there a way I could pass the catalog object as subject to my tests without dumping it to a file?
I am not very clear as to what exactly you are trying to achieve but from my understanding I feel that using a before(:each) hook might be of use to you. You can define variables in this block that are available to all the stories in that scope.
Here is an example:
require "rspec/expectations"
class Thing
def widgets
#widgets ||= []
end
end
describe Thing do
before(:each) do
#thing = Thing.new
end
describe "initialized in before(:each)" do
it "has 0 widgets" do
# #thing is available here
#thing.should have(0).widgets
end
it "can get accept new widgets" do
#thing.widgets << Object.new
end
it "does not share state across examples" do
#thing.should have(0).widgets
end
end
end
You can find more details at:
https://www.relishapp.com/rspec/rspec-core/v/2-2/docs/hooks/before-and-after-hooks#define-before(:each)-block
I have the following code. I want to use the methods for myserver1 and myserver2 and pass them the addresses the have been iterated through in the send_to_servers method. I can't seem to do that. Please help. Unless I can have these two receive addresses on an altering basis, I won't be able to do what I need to do. Thanks in advance.
class Addresses
def add
#addresses = %w(me#gmail.com me2#gmail.com me3#gmail.com)
end
def myserver1
puts "Sending email from myserver1 with address #{#address}"
end
def myserver2
puts "Sending email from myserver2 with address #{#address}"
end
def servers
serv = [myserver1, myserver2]
end
# def servers
# serv = (1..2).to_a # Your list of servers goes here
# end
def send_to_servers(servers)
#addresses.each.with_index do |address, i|
server = servers[i % servers.length]
puts "Sending address #{address} to server #{server}"
#address = address
end
end
end
a = Addresses.new
a.add
servers = a.servers
a.send_to_servers(servers)
It's unclear how you might want to structure it, but here is a concise implementation that I think meets your description.
def myserver1(address)
# Do something with address
end
def myserver2(address)
# Do something with address
end
addresses = %w(me#gmail.com me2#gmail.com me3#gmail.com)
servers = %w(myserver1 myserver2).cycle
addresses.each do |address|
send(servers.next, address)
end
Apologies if I'm missing something crucial to your problem with this. Please feel free to comment on what extra functionality is required to help firm up the specification.
Your problem is that your server methods do not return anything:
def myserver1
puts "Sending email from myserver1 with address #{#address}"
end
This method prints the message out and returns nil. puts always returns nil.
Thus, when you do [myserver1, myserver2], it prints out two messages and returns [nil, nil].
Servers are things, they should probably be objects, not methods. Methods are actions that do and/or return something. Try something like this:
class Server
def initialize(name)
#name = name
end
def send_address(address)
puts "Sending email from #{#name} with address #{address}"
end
end
addresses = %w(me#gmail.com me2#gmail.com me3#gmail.com)
servers = [Server.new("server one"), Server.new("server two")]
addresses.each_with_index do |address, i|
server = servers[i % servers.length]
server.send_address(address)
end
I believe your code should be refactored to be closer to the real world.
You want to loop through a list of email addresses and send each iteration to myserver2 and myserver2.
This means you need to have the cartesian product of emails and servers and do "send" to that pair.
require 'net/smtp'
emails = %w{email1#email.com email2#email.com}
servers = %w{server1 server2}
emails.product(servers).each do |address, server|
Net::SMTP.start(server) do |smtp|
smtp.send_message 'Body', 'from#example.com', [address]
end
end