Ruby publish/subscribe implementation issue - ruby

I'm experimenting with Observable Ruby class that should be capable to provide Publish/Subscribe pattern.
I wrote this code to test the behavior:
require "observer"
class Ticker ### Periodically fetch a stock price.
include Observable
attr_accessor :sleep_time
def initialize(symbol, sleep_time)
#symbol = symbol
#sleep_time = sleep_time
end
def run
last_price = nil
loop do
price = Price.fetch(#symbol)
print "#{#symbol}, Current price: #{price}\n"
if price != last_price
changed # notify observers
last_price = price
notify_observers(price)
end
sleep #sleep_time
end
end
end
class Price ### A mock class to fetch a stock price (60 - 140).
def self.fetch(symbol)
60 + rand(80)
end
end
class Notification
attr_accessor :name, :sleep_time
def initialize(ticker, name, sleep_time)
#name = name
#sleep_time = sleep_time
ticker.add_observer(self)
end
def update(price)
puts #name + ': ' + price.to_s
sleep #sleep_time
end
end
ticker = Ticker.new("MSFT", 0.5)
t1 = Thread.new { slow_notification = Notification.new(ticker, 'thread 1', 0) }
t2 = Thread.new { fast_notification = Notification.new(ticker, 'thread 2', 5) }
ticker.run
I was expecting that fast_notification sent the notification faster than slow_notification cause the sleep time for the first is one is 0 seconds and for the other one 5 seconds. In practice, they run at the same time (every 5 seconds).

In order to handle it in an async way, the thread creation should be within the main loop. I got the expected behavior with these changes:
require "observer"
class Ticker ### Periodically fetch a stock price.
include Observable
attr_accessor :sleep_time
def initialize(symbol, sleep_time)
#symbol = symbol
#sleep_time = sleep_time
end
def run
last_price = nil
loop do
price = Price.fetch(#symbol)
#print "#{#symbol}, Current price: #{price}\n"
if price != last_price
changed # notify observers
last_price = price
Thread.new { notify_observers(price, Time.now) }
end
sleep #sleep_time
end
end
end
class Price ### A mock class to fetch a stock price (60 - 140).
def self.fetch(symbol)
7600 + rand(800)
end
end
class Notification
attr_accessor :name, :sleep_time, :working, :mutex, :counter
def initialize(ticker, name, sleep_time)
#name = name
#sleep_time = sleep_time
#working = false
#mutex = Mutex.new
#counter = 0
ticker.add_observer(self)
end
def do_something(price)
puts #name + ': ' + price.to_s
sleep #sleep_time
end
def update(price, time)
#mutex.synchronize{
do_something(price)
}
end
end
ticker = Ticker.new("MSFT", 0.5)
Notification.new(ticker, 'Fast notification', 0)
Notification.new(ticker, 'Slow notification', 5)
ticker.run

I think you should flush the buffer. Try add STDOUT.flush after print.
def run
last_price = nil
loop do
price = Price.fetch(#symbol)
print "#{#symbol}, Current price: #{price}\n"
STDOUT.flush
if price != last_price
changed # notify observers
last_price = price
notify_observers(price)
end
sleep #sleep_time
end
end

Related

Ruby - Object-oriented Store example

I am starting my studies with Ruby and OO and I have received a test to do about OO.
It is a store scenario where I have to create the classes, methods, calls ...
For now I have create the class Payment and Product classes (Physical, Membership, Digital)
class Payment
attr_reader :authorization_number, :amount, :invoice, :order, :payment_method, :paid_at, :shipping
def initialize(attributes = {})
#authorization_number, #amount = attributes.values_at(:authorization_number, :amount)
#invoice, #order = attributes.values_at(:invoice, :order)
#payment_method = attributes.values_at(:payment_method)
#shipping.extend Shipping
end
def pay(paid_at = Time.now)
#amount = order.total_amount
#authorization_number = Time.now.to_i
#invoice = Invoice.new(order.get_address, order.get_address, order)
#paid_at = paid_at
order.close(#paid_at)
extend
def paid?
!paid_at.nil?
end
def prepare_shipping(order)
???
end
end
class Product
attr_reader :name, :description, :type
def initialize(name, description, type)
#name = name
#description = description
#type = type
end
end
class Digital < Product
include DigitalProduct
def initialize(name, description, type)
super(name, description, type)
end
def matches?(query)
query=="digital"
end
end
class Physical < Product
def initialize(name, description, type)
super(name, description, type)
end
def matches?(query)
query=="physical"
end
end
class Membership < Product
attr_reader :membership_status
include DigitalProduct
def initialize(name, description, type)
super(name, description, type)
end
def matches?(query)
query=="membership"
end
def activate_membership()
#membership_status = true;
end
end
Here is my problem:
1.If the product is physical I have to generate a shipping label.
1.1 If its a book I have to notify that this product doesn't have taxes.
2.If the product is membership I have to activate the signature and notify via email the buyer.
If the product is digital I have to send a email for the buyer and give $10 discount in this product.
I am doing pretty good, but now I would like your opinion to know what is the best way following OO techniques (SOLID) to create the points above.
ps: I tried to create but my solution was really poor with many ifs.
I think there is a better way to do it with
I have other classes if you need more info tell me please.
Sorry about my poor english.
I have developed this solution, I think it is good ... maybe you can give me a feedback about it.
So to solve the problem about shipping and discount rules I created 2 modules Shipping and Discount
module Discount
def prepare_discount(order)
total_discount = 0
order.get_items.each do |item|
total_discount += item.product.discount
end
return total_discount
end
def discount
if matches?("physical")
return discount_for_physical_product
elsif matches?("digital")
return discount_for_digital_product
elsif matches?("membership")
return discount_for_membership_product
elsif matches?("default")
return discount_for_default_product
end
end
def discount_for_physical_product
price_discount_for_physical_product = 0
return price_discount_for_physical_product
end
def discount_for_digital_product
price_discount_for_digital_product = 10.00
return price_discount_for_digital_product
end
def discount_for_default_product
price_discount_for_digital_product = 10.00
return price_discount_for_digital_product
end
def discount_for_membership_product
price_discount_for_membership_product = 0
return price_discount_for_membership_product
end
end
module Shipping
def prepare_shipping(order)
order.get_items.each do |item|
item.product.shipping
end
end
def shipping
if matches?("physical")
shipping_for_physical_product
elsif matches?("digital")
shipping_for_digital_product
elsif matches?("membership")
shipping_for_membership_product
elsif matches?("default")
shipping_for_default_product
end
end
def shipping_for_physical_product
case #type
when :book
create_shipping_label
notify_buyer_product_without_taxes
else
create_shipping_label
end
end
def shipping_for_digital_product
notify_buyer_via_email
end
def shipping_for_membership_product
notify_buyer_via_email
activate_membership
end
def shipping_for_default_product
create_shipping_label
notify_buyer_via_email
end
def create_shipping_label
puts "Creating shipping label ..."
end
def notify_buyer_product_without_taxes
puts "Product exempt from taxes, as provided in the Constitution Art. 150, VI, d."
end
def notify_buyer_via_email
puts "Sending email ..."
end
end
This way Product and Order can extend both modules and implement the logic.

How to update Ruby GTK window label in real time and communicate with other threads

I am building a Ruby application which consists of the code responsible for the logic of the program and the one for GUI. Both parts of the code are split in classes and run in separate threads.
Ruby Gtk library is very poorly documented. I want to know how to update specific Gtk elements in real time (for instance, text in a label, which is in a window). I want to update a specific element every second.
I also want to find out how can threads exchange data. I have tried using Queue library. When I use it, I run into an error in the console:
undefined local variable or method `queue' for #<TimerWindow:0xa36d634 ptr=0xb4201178>
Program code:
require_relative 'notifications'
require_relative 'settings_reader'
require 'gtk3'
require "thread"
q = Queue.new
class TimerWindow < Gtk::Window
def initialize
#label = ""
super
init_ui
end
def init_ui
fixed = Gtk::Fixed.new
add fixed
button = Gtk::Button.new :label => "Quit"
button.set_size_request 80, 35
button.signal_connect "clicked" do
#label.set_text q.pop
end
fixed.put button, 50, 50
set_title "Tomatono"
signal_connect "destroy" do
Gtk.main_quit
end
set_border_width 10
#label = Gtk::Label.new "HEY"
fixed.put #label, 20, 20
set_default_size 250, 200
set_window_position :center
show_all
end
end
class Timer
def initialize
# Current time in seconds
#time = 0
settings = Settings_Reader.new
#work_time = Integer(settings.work_time) * 60
#break_time = Integer(settings.break_time) * 60
#work_text = settings.work_text
#return_text = settings.return_text
#break_text = settings.break_text
#work_notif_header = settings.work_notif_header
#break_notif_header = settings.break_notif_header
#status_notif_header = settings.status_notif_header
#work_status = settings.work_status
#break_status = settings.break_status
end
def launch
while true
work_time()
break_time()
end
end
def work_time()
puts #work_text
notification = Notif.new(#work_notif_header, #work_text)
notification.post
#time = 0
sleep(1)
while #time < #work_time
#time += 1
puts "#{min_remaining()} minutes remaining" if (#time % 60) == 0
if (#time % 60) == 0
notification = Notif.new(#work_notif_header, "#{#work_status} #{#time / 60} minutes.")
notification.post
end
q << #time
sleep(1)
end
end
def break_time
puts #break_text
#time = 0
sleep(1)
while #time < #break_time
#time += 1
puts "#{min_remaining()} minutes remaining" if (#time % 60) == 0
notification = Notif.new(#break_notif_header, "#{#break_status} #{#time / 60} minutes.")
notification.post
q << #time
sleep(1)
end
end
def reset
end
def stop_time
end
def min_remaining()
(1500 - #time) / 60
end
end
app = Thread.new {
timer = Timer.new
timer.launch
}
gui = Thread.new {
Gtk.init
window = TimerWindow.new
#window.update
Gtk.main
}
app.join
gui.join
Whenever I press the "Quit" button, I want the label text to change to the value set in the q variable, set in the Timer class (in the while loop). But it throws out an error that variable does not exist. Should it not be global?
No It is a local variable:
myglobal = "toto"
class Myclass
def initialize
#myvar = myglobal
end
def print
puts #myvar
end
end
an_instance = Myclass.new
an_instance.print
throw this error:
global_and_class.rb:5:in `initialize': undefined local variable or method `myglobal' for #<Myclass:0x00000000a74ce8> (NameError)
But it works if you specify myglobal as a global variable:
$myglobal = "toto"
class Myclass
def initialize
#myvar = $myglobal
end
def print
puts #myvar
end
end
an_instance = Myclass.new
an_instance.print
But you should be carefull with the use of global variable. Why not use the Queue instance as an argument for the initialize method ?
** Edit **
First of all here is a simple example that works with just a local variable:
#!/usr/bin/env ruby
require "gtk3"
label = Gtk::Label.new("test")
othert = Thread.new {
loop {
puts 'thread running';
label.text = Time.now.to_s; sleep 1 }
}
maint = Thread.new {
win = Gtk::Window.new
win.set_default_size 100, 30
win.add(label)
win.show_all
win.signal_connect("destroy") {othert.kill;Gtk.main_quit}
Gtk.main
}
maint.join
othert.join
Maybe you should start from this and see how to create you classes.
Edit 2
class TimerWindow < Gtk::Window
def initialize(label)
super()
add(label)
end
end
alabel = Gtk::Label.enw("test")
othert = Thread.new {
loop {
puts 'thread running';
label.text = Time.now.to_s; sleep 1 }
}
maint = Thread.new {
win = TimerWindow.new(alabel)
win.set_default_size 100, 30
win.show_all
win.signal_connect("destroy") {othert.kill;Gtk.main_quit}
Gtk.main
}
maint.join
othert.join

How do I use the inject method to calculate the balance instead of what I have now?

In the balance function, I am trying to calculate the balance using the inject method instead. Tried the documentation but couldn't get it..
I am new to Ruby and programming so any help would be appreciated..
class BankAccount
attr_reader :name
def initialize(name)
#name = name
#transactions = []
add_transaction("Beginning Balance", 0)
end
def credit(description, amount)
add_transaction(description, amount)
end
def debit(description, amount)
add_transaction(description, -amount)
end
def add_transaction(description, amount)
#transactions.push(description: description, amount: amount)
end
def balance
balance = 0.0
#transactions.each do |transaction|
balance += transaction[:amount]
end
return balance
end
def to_s
"Name: #{name}, Balance: #{sprintf("%0.2f", balance)}"
end
def print_register
puts "#{name}'s Bank Account"
puts "-" * 40
puts "Description".ljust(30) + "Amount".rjust(10)
#transactions.each do |transaction|
puts transaction[:description].ljust(30) + sprintf("%0.2f", transaction[:amount]).rjust(10)
end
puts "-" * 40
puts "Balance:".ljust(30) + sprintf("%0.2f", balance).rjust(10)
puts "-" * 40
end
end
Your example is great, as it's very easy to change the loop you have to an inject call:
def balance
#transactions.inject(0.0) { |balance, transaction| balance + transaction[:amount] }
end

Difference between parameters "amount" and "amount=0", in this context?

What's the difference that the parameter "amount" being defined to 0 has on the overall instance creation in this context, since the code below the commented line does the same thing with out "amount=0"?
class Account
attr_accessor :balance
def initialize(amount=0)
self.balance = amount
end
def +(x)
self.balance += x
end
def -(x)
self.balance -= x
end
def to_s
balance.to_s
end
end
acc = Account.new(20)
acc -= 5
puts acc
class Account
attr_accessor :balance
def initialize(amount)
self.balance = amount
end
def +(x)
self.balance += x
end
def -(x)
self.balance -= x
end
def to_s
balance.to_s
end
end
acc = Account.new(20)
acc -= 5
puts acc
I'm a beginner. Thanks for any help!
Specifying amount = 0 in the parameter list make the amount parameter become optional (with 0 as its default value).
If you don't specify amount argument, it will be 0.
account = Account.new # without amount argument
account.balance # => 0
account = Account.new 10 # with amount argument
account.balance # => 10
The only difference is:
The first class sets the default value
class Account
attr_accessor :balance
def initialize(amount=0)
self.balance = amount
end
... class omitted
end
account = Account.new
account.balance # should be equal to 0
class Account
attr_accessor :balance
def initialize(amount)
self.balance = amount
end
... class omitted
end
account = Account.new nil
account.balance # should be equal to nil

Rails Activerecord observer not working

In rails 3.1 app,
I am trying to create an observable activerecord observer, but seems it doesn't work.
Even creating only activerecord observer without being observable, the after_create
event is not called, the string "in after create event" is never printed out.
The caller is a rake task.
Here's the code sample to make it clear.
class PostTemp < ActiveRecord::Base
end
class PostTempObserver < ActiveRecord::Observer
include Observable
attr_reader :new_data
def initialize
#new_data = 0
end
def after_create(record)
#binding.pry
puts "in after create event"
#new_data = 1
notify_observers(#new_data)
#new_data = 0
end
def reset_new_data
#new_data = 0
end
end
class Notifier
attr_reader :total_new_data
def initialize(model_observer)
model_observer.add_observer(self)
#total_new_data = 0
end
def update(new_data_flag)
#total_new_data = #total_new_data + new_data_flag
end
def perform
if #total_new_data > 0
#send notification
puts "notify to bar app..."
#total_new_data = 0
else
puts "no new data"
end
end
end
#in config/application.rb
config.active_record.observers = :post_temp_observer
in task1.rake
namespace :foo do
desc "crawl message and notify if there's new data"
task :get_message => :environment do
post_temp_observer = PostTempObserver.instance
notifier = Notifier.new(post_temp_observer)
#..
#do some crawling
#..
notifier.perform
end
end
I figured it out myself.
I need to add super and changed in PostTempObserver
class PostTempObserver < ActiveRecord::Observer
include Observable
attr_reader :new_data
def initialize
super
#new_data = 0
end
def after_create(post_temp)
#new_data = 1
changed
notify_observers(#new_data)
#new_data = 0
end
private
def reset_new_data
#new_data = 0
end
end

Resources