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
Related
I'm creating a simple task manager in Ruby and am having trouble running a class method on an object that I've created. Here's my backend code:
class TM::Project
attr_accessor :name, :pid, :tasks
##projects = []
def initialize(name=nil)
#name = name
#pid = ##projects.size
#tasks = []
##projects << self
end
def add_task(priority, description)
task = TM::Task.new(priority, description)
#tasks << task
end
end
class TM::Task
attr_accessor :tid, :description, :priority, :status, :creation_date
##tasks = []
def initialize(priority, description)
#description = description
#priority = priority
#tid = ##tasks.size
##tasks << self
#status = "incomplete"
#creation_date = Time.now
end
end
And here's the code for the console:
require_relative 'lib/task-manager.rb'
class Task_io
##io_projects = []
def start
puts "Welcome to Task Manager. How may we assist you?\n\n"
puts "Available Commands: \n"
puts " create NAME - Create a new project with name=NAME"
puts " add PID PRIORITY DESC - Add a new task to project with id=PID"
puts 'Enter request>'
req = gets.chomp
input = req.split
if input[0] == 'create'
proj = TM::Project.new(input[1])
puts "#{proj.name} was created with ID=#{proj.pid}"
##io_projects << proj
self.start
elsif input[0] == 'add'
i = 0
proj_id = input[1].to_i
##io_projects[proj_id].add_task(input[2], input[3].to_s)
puts "#{input[3].to_s} was assigned TID #{##tasks[i + 1]}"
i += 1
self.start
else
puts "Invalid request, please try again"
self.start
end
end
end
I'm able to create projects, but when I try to add a task to the project I get the following error:
undefined method `add_task' for nil:NilClass
Am I accessing the object incorrectly?
I'm trying to figure out a way to have a method trigger another method by creating a new Listener class. I'd really like the code to be simplified and not involve adding anything specific to the callback method or the trigger method. Basically, what I'm trying to do is this:
def level_up
level += 1
end
def print_level
puts "Level Up! (#{level})"
end
notify_level = Listener.new(:level_up, :print_level);
What my Listener class is (right now) is this:
# Listener.new(attached_to, callbacks)
class Listener
def initialize(attached_to, function)
#owner, #callback = attached_to, function
end
def owner
#owner
end
def callback
#callback
end
def trigger
# execute callback manually
self.method(#owner).call
self.method(#callback).call
end
end
In order to call both, I need to execute notify_level.trigger itself, but what I want is to execute level_up and call print_level. I know someone will mention something about observers, but I need more than just that. I want to hold fast to DRY. Manually adding observers and listeners to every single method is just terrible, especially since I can't add or remove them with ease.
Personally I'm not a big fan of this pattern but this is kind of a fun question so here is my solution. Should work in Ruby 1.9 and greater.
module MethodListener
##observed_methods = {}
def method_added(method)
alias_name = "__#{method}_orig"
return if method_defined?(alias_name) || method.match(/__.*_orig/)
alias_method alias_name, method
define_method(method) do |*args|
ret = send(alias_name, *args)
(##observed_methods[method] || []).each {|callback| send(callback)}
ret
end
end
def listen(owner, callback)
(##observed_methods[owner] ||= []) << callback
end
end
Usage example:
class A
extend MethodListener
def b(a,b)
puts "b #{a} #{b}"
true
end
def c
puts 'c'
end
listen :b, :c
end
A.new.b(1,2) # => true
# Prints:
# b 1 2
# c
I changed my original code to be more semantic and so it would make more sense.
class Event
def initialize(event, callback_array = [])
if callback_array.kind_of? Array
#callbacks = callback_array
else
#callbacks = [callback_array]
end
#event = event
end
def trigger(*args)
self.method(#event).call *args
#callbacks.each{ |callback|
if callback.instance_of? Event
callback.trigger *args
else
method(callback).call *args
end
}
end
def add(callback)
#callbacks.push callback
end
def remove(callback)
#callbacks.delete_at(#callbacks.index(callback) || #callbacks.length)
end
def event_name
#event
end
end
Usage:
$infinite_break = 10
def infinite_loop_a(type)
puts "#{$infinite_break} points of #{type} damage taken"
$infinite_break -= 1
if $infinite_break > 0
$infinite.trigger(type)
else
$infinite.remove(:infinite_loop_a)
end
end
def infinite_loop_b(type)
puts "player is dealing #{$infinite_break} damage"
end
$infinite = Event.new(:infinite_loop_b, :infinite_loop_a)
$infinite.trigger('fire')
Also, I know I'm calling the infinite_loop_b inside infinite_loop_a, but that's for a specific reason. The Event instances can have another Event as a callback.
I am making a short, text-based game as an extra credit exercise based on the Ruby I have learned so far and I'm having trouble getting classes to read and write variables between each other. I have read extensively and searched for clarification on how to do this but I haven't had much luck. I have tried using # instance variables and attr_accessible but I can't figure it out. Here is my code so far:
class Game
attr_accessor :room_count
def initialize
#room_count = 0
end
def play
while true
puts "\n--------------------------------------------------"
if #room_count == 0
go_to = Entrance.new()
go_to.start
elsif #room_count == 1
go_to = FirstRoom.new()
go_to.start
elsif #room_count == 2
go_to = SecondRoom.new()
go_to.start
elsif #room_count == 3
go_to = ThirdRoom.new()
go_to.start
end
end
end
end
class Entrance
def start
puts "You are at the entrance."
#room_count += 1
end
end
class FirstRoom
def start
puts "You are at the first room."
#room_count += 1
end
end
class SecondRoom
def start
puts "You are at the second room."
#room_count += 1
end
end
class ThirdRoom
def start
puts "You are at the third room. You have reached the end of the game."
Process.exit()
end
end
game = Game.new()
game.play
I want to have the different Room classes change the #room_count variable so that Game class knows which room to go to next. I am also trying to do this without implementing class inheritance. Thanks!
class Room
def initialize(game)
#game = game
#game.room_count += 1
end
def close
#game.room_count -= 1
end
end
class Game
attr_accessor :room_count
def initialize
#room_count = 0
end
def new_room
Room.new self
end
end
game = Game.new
game.room_count # => 0
room = game.new_room
game.room_count # => 1
room.close
game.room_count # => 0
I'm creating a card game with multiple classes. Currently, I'm using global variables to hold the $shuffled_deck, $players_hand, and $dealers_hand variables, but I worry when using global variables (perhaps, needlessly) and would prefer to use instance variables.
I've been reading around, but nothing is really clicking. Can anyone help point me in the right direction with this?
Using instance variables I haven't been able to save the #players_hand and #dealers_hand to be able to use them in other classes. For instance, I have #players_hand from the Player class. I have the Dealer class draw a card, but I can't pull that #players_hand into the Dealer class to add the two together.
My current code is:
class Blackjack
def initialize
#player = Player.new
#dealer = Dealer.new
end
end
class Dealer
def initialize
#deck = Deck.new
$dealers_hand = 0
end
def hit_dealer
#deck.hit_dealer
end
def hit_player
#deck.hit_player
end
def draw_card
#hit = $shuffled_deck
end
def shuffle
#deck.suits
end
end
class Player
def initialize
$players_hand = 0
end
end
class Deck
def suits
#code that shuffled the deck..
$shuffled_deck = #shuffled_deck
end
def hit_player
#hit = $shuffled_deck.pop
end
def hit_dealer
#hit = $shuffled_deck.pop
end
end
using your example you can do it like this
class Blackjack
attr_reader :player, :dealer
def initialize
#player = Player.new
#dealer = Dealer.new
end
end
class Dealer
def dealers_hand #the long java way of a getter
#dealers_hand
end
#and now the short ruby way
attr_reader :dealers_hand #if you only need to read the attribute
attr_writer :dealers_hand #if you only need to write the attribute
attr_accessor: dealers_hand #if you need both
def initialize
#deck = Deck.new
#dealers_hand = 5
end
def hit_dealer
#deck.hit_dealer
end
def hit_player
#deck.hit_player
end
def draw_card
#hit = $shuffled_deck
end
def shuffle
#deck.suits
end
end
class Player
attr_reader :players_hand
def initialize
#players_hand = 0
end
end
class Deck
def suits
attr_reader :shuffled_deck
#shuffled_deck = #shuffled_deck
end
def hit_player
#hit = $shuffled_deck.pop
end
def hit_dealer
#hit = $shuffled_deck.pop
end
end
game = Blackjack.new
p game.dealer.dealers_hand
game.dealer.dealers_hand = 4
p game.dealer.dealers_hand
You want to use attr_reader, attr_writer, or attr_accessor. Here's how they work:
attr_reader :players_hand: Allows you to write some_player.players_hand to get the value of that player's players_hand instance variable
attr_writer :players_hand: Allows you to write some_player.players_hand = 0 to set the variable to 0
attr_accessor :players_hand: Allows you to both read and write, as though you'd used both attr_reader and attr_writer.
Incidentally, all these do is write methods for you. If you wanted, you could do it manually like this:
class Player
def initialize
#players_hand = 0
end
def players_hand
#players_hand
end
def players_hand=(new_value)
#players_hand = new_value
end
end
I'm trying to add logging to a method from the outside (Aspect-oriented-style)
class A
def test
puts "I'm Doing something..."
end
end
class A # with logging!
alias_method :test_orig, :test
def test
puts "Log Message!"
test_orig
end
end
a = A.new
a.test
The above works alright, except that if I ever needed to do alias the method again, it goes into an infinite loop. I want something more like super, where I could extend it as many times as I needed, and each extension with alias its parent.
Another alternative is to use unbound methods:
class A
original_test = instance_method(:test)
define_method(:test) do
puts "Log Message!"
original_test.bind(self).call
end
end
class A
original_test = instance_method(:test)
counter = 0
define_method(:test) do
counter += 1
puts "Counter = #{counter}"
original_test.bind(self).call
end
end
irb> A.new.test
Counter = 1
Log Message!
#=> #....
irb> A.new.test
Counter = 2
Log Message!
#=> #.....
This has the advantage that it doesn't pollute the namespace with additional method names, and is fairly easily abstracted, if you want to make a class method add_logging or what have you.
class Module
def add_logging(*method_names)
method_names.each do |method_name|
original_method = instance_method(method_name)
define_method(method_name) do |*args,&blk|
puts "logging #{method_name}"
original_method.bind(self).call(*args,&blk)
end
end
end
end
class A
add_logging :test
end
Or, if you wanted to be able to do a bunch of aspects w/o a lot of boiler plate, you could write a method that writes aspect-adding methods!
class Module
def self.define_aspect(aspect_name, &definition)
define_method(:"add_#{aspect_name}") do |*method_names|
method_names.each do |method_name|
original_method = instance_method(method_name)
define_method(method_name, &(definition[method_name, original_method]))
end
end
end
# make an add_logging method
define_aspect :logging do |method_name, original_method|
lambda do |*args, &blk|
puts "Logging #{method_name}"
original_method.bind(self).call(*args, &blk)
end
end
# make an add_counting method
global_counter = 0
define_aspect :counting do |method_name, original_method|
local_counter = 0
lambda do |*args, &blk|
global_counter += 1
local_counter += 1
puts "Counters: global##{global_counter}, local##{local_counter}"
original_method.bind(self).call(*args, &blk)
end
end
end
class A
def test
puts "I'm Doing something..."
end
def test1
puts "I'm Doing something once..."
end
def test2
puts "I'm Doing something twice..."
puts "I'm Doing something twice..."
end
def test3
puts "I'm Doing something thrice..."
puts "I'm Doing something thrice..."
puts "I'm Doing something thrice..."
end
def other_tests
puts "I'm Doing something else..."
end
add_logging :test, :test2, :test3
add_counting :other_tests, :test1, :test3
end
First choice: subclass instead of overriding:
class AWithLogging < A\
def test
puts "Log Message!"
super
end
end
Second choice: name your orig methods more carefully:
class A # with logging!
alias_method :test_without_logging, :test
def test
puts "Log Message!"
test_without_logging
end
end
Then another aspect uses a different orig name:
class A # with frobnication!
alias_method :test_without_frobnication, :test
def test
Frobnitz.frobnicate(self)
test_without_frobnication
end
end