For the XMPP interface for the Stack Overflow chat I am parsing the JSON feed from chat and generating Ruby objects for every chat events, such as messages sent, edits sent, users logging in or out, etc. I also generate events for "slash-commands" sent to the XMPP server, like "/help" or "/auth" in order to allow the XMPP user to authenticate with their Stack Overflow chat account.
I have set up these classes in a hierarchy I feel makes good logical sense:
class SOChatEvent # base class
|
|--- class SOXMPPEvent # base for all events that are initiated via XMPP
| |
| |--- class SOXMPPMessage # messages sent to the XMPP bridge via XMPP
| | |
| | |--- class SOXMPPMessageToRoom # messages sent from an XMPP user to an XMPP MUC
| | |
| | |--- class SOXMPPUserCommand # class for "slash commands", that is, messages starting
| | | | # with /, used for sending commands to the bridge
| | | |
| | | |--- class SOXMPPUserHelpCommand
| | | |--- class SOXMPPUserLoginCommand
| | | |--- class SOXMPPUserBroadcastCommand
|
|--- class SOChatRoomEvent # base class for all events that originate from an SO chat room
| |
| |--- class SOChatMessage # messages sent to an SO chat room via the SO chat system
| | |
| | |--- class SOChatMessageEdit # edits made to a prior SOChatMessage
| |
| |--- class SOChatUserEvent # events related to SO chat users
| | |
| | |--- class SOChatUserJoinRoom #Event for when a So user joins a room
| | |--- class SOChatUserLeaveRoom #Event for when a So user leaves a room
(etc)
You can see the full hierarchy and source in Trac or via SVN.
My question is twofold: First, what is the best way to instantiate these events? What I'm currently doing is parsing the JSON events using a giant switch statement --well, it's ruby so it's a case statement -- and, it's not giant yet, but it will be if I continue this way:
rooms.each do |room|
rid = "r"+"#{room.room_id}"
if !data[rid].nil?
#last_update = data[rid]['t'] if data[rid]['t']
if data[rid]["e"]
data[rid]["e"].each do |e|
puts "DEBUG: found an event: #{e.inspect}"
case e["event_type"]
when 1
event = SOChatMessage.new(room,e['user_name'])
event.encoded_body = e['content']
event.server = #server
events.push event
when 2
event = SOChatMessageEdit.new(room,e['user_name'])
event.encoded_body = e['content']
event.server = #server
events.push event
when 3
user = SOChatUser.new(e['user_id'], e['user_name'])
event = SOChatUserJoinRoom.new(room,user)
event.server = #server
events.push event
when 4
user = SOChatUser.new(e['user_id'], e['user_name'])
event = SOChatUserLeaveRoom.new(room,user)
event.server = #server
events.push event
end
end
end
end
end
But I imagine there has to be a better way to handle that! Something like SOChatEvent.createFromJSON( json_data )... But, what's the best way to structure my code so that objects of the proper subclass are created in response to a given event_type?
Second, I'm not actually using ant subclasses of SOXMPPUserCommand yet. Right now all commands are just instances of SOXMPPUserCommand itself, and that class has a single execute method which switches based upon regex of the command. Much the same problem -- I know there's a better way, I just am not sure what the best way is:
def handle_message(msg)
puts "Room \"#{#name}\" handling message: #{msg}"
puts "message: from #{msg.from} type #{msg.type} to #{msg.to}: #{msg.body.inspect}"
event = nil
if msg.body =~ /\/.*/
#puts "DEBUG: Creating a new SOXMPPUserCommand"
event = SOXMPPUserCommand.new(msg)
else
#puts "DEBUG: Creating a new SOXMPPMessageToRoom"
event = SOXMPPMessageToRoom.new(msg)
end
if !event.nil?
event.user = get_soxmpp_user_by_jid event.from
handle_event event
end
end
and:
class SOXMPPUserCommand < SOXMPPMessage
def execute
case #body
when "/help"
"Available topics are: help auth /fkey /cookie\n\nFor information on a topic, send: /help <topic>"
when "/help auth"
"To use this system, you must send your StackOverflow chat cookie and fkey to the system. To do this, use the /fkey and /cookie commands"
when "/help /fkey"
"Usage: /fkey <fkey>. Displays or sets your fkey, used for authentication. Send '/fkey' alone to display your current fkey, send '/fkey <something>' to set your fkey to <something>. You can obtain your fkey via the URL: javascript:alert(fkey().fkey)"
when "/help /cookie"
"Usage: /cookie <cookie>. Displays or sets your cookie, used for authentication. Send '/cookie' alone to display your current fkey, send '/cookie <something>' to set your cookie to <something>"
when /\/fkey( .*)?/
if $1.nil?
"Your fkey is \"#{#user.fkey}\""
else
#user.fkey = $1.strip
if #user.authenticated?
"fkey set to \"#{#user.fkey}\". You are now logged in and can send messages to the chat"
else
"fkey set to \"#{#user.fkey}\". You must also send your cookie with /cookie before you can chat"
end
end
when /\/cookie( .*)?/
if $1.nil?
"Your cookie is: \"#{#user.cookie}\""
else
if $1 == " chocolate chip"
"You get a chocolate chip cookie!"
else
#user.cookie = $1.strip
if #user.authenticated?
"cookie set to \"#{#user.cookie}\". You are now logged in and can send messages to the chat"
else
"cookie set to \"#{#user.cookie}\". You must also send your fkey with /fkey before you can chat"
end
end
end
else
"Unknown Command \"#{#body}\""
end
end
end
I know there's a better way to do this, just not sure what specifically it is. Should the responsibility of creating subclasses of SOXMPPUserCommand fall on SOXMPPUserCommand itself? Should all subclasses register with the parent? Do I need a new class?
What's the best way to instantiate objects of subclasses in such a hierarchal structure?
Addressing your first question. Here's some ideas you might like to consider
First, structure you sub-classes so they all use the same initiation parameters. Also, you could put some of the other initiating code there as well (such as your encoded_body and server accessors. Here's a skeleton of what I mean:
# SOChat Class skeleton structure
class SOChatSubClass #< inherit from whatever parent class is appropriate
attr_accessor :encoded_body, :server, :from, :to, :body
def initialize(event, room, server)
#encoded_body = event['content']
#server = server
SOChatEvent.events.push event
#class specific code
xmpp_message = event['message']
#from = xmpp_message.from
#to = xmpp_message.to
#body = xmpp_message.body
#use super to call parent class initialization methods and to DRY up your code
end
end
Note that in my example you'll still have duplicated code in the sub-classes. Ideally you'd pull out the duplication by putting it in the appropriate parent class.
If you have problems creating a common list of initiation parameters, then rather than pass in a list of arguments (event, room, server), change the classes to accept an argument list as a hash {:event => event, :room => room, :server => server, etc}.
Regardless, once you have a common parameter structure for initializing the classes, you can initialize them a bit more dynamically, eliminating the need for the case statement.
class SOChatEvent
class << self; attr_accessor :events; end
#events = []
##event_parser = {
0 => SOChatSubClass, #hypothetical example for testing
1 => SOChatMessage,
2 => SOChatMessageEdit,
#etc
}
def self.create_from_evt( json_event_data, room=nil, server=nil)
event_type = json_event_data["event_type"]
event_class = ##event_parser[event_type]
#this creates the class defined by class returned in the ##event_parser hash
event_obj = event_class.new(json_event_data, room, server)
end
#rest of class
end
##event_parser contains the mapping between event type and the class to implement that event type. You just assign the appropriate class to a variable and treat it just like the actual class.
Code like the following would create an object of the appropriate class:
event_obj = SOChatEvent.create_from_evt( json_event_data,
"some room",
"some server")
Note: There are further optimizations that could be done to what I provided to be even cleaner and more concise, but hopefully this helps you get over the hump of the case statement.
Edit: I forgot to mention the Class instance variable SOChatEvent.events created with this:
class << self; attr_accessor :events; end
#events = []
You were pushing events to an event stack, but I wasn't clear where you wanted that stack to exist and whether it was a global events list, or specific to a particular class. The one I did is global, so feel free to change it if you wanted the event stack constrained to certain classes or instances.
Related
Many simmilar Q/A on this topic here and there, but I was unable to find exact solution for my problem. Using Rails 3.0.9 now, and trying to upgrade existing older application(not Rails).
The goal is to send simple email to new clients created by admins.
Have been following this oficial guide (and many others), but with no success.
The issue is, that method(s) defined in this controller, from class 'UserMailer', aren`t recognised from another controller, while class 'UserMailer' itself recognised is(how do I know this, will be explained below):
/app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default :from => "info#xxxxx.sk"
def kokotina # << this is just a dummy method for testing
caf = "ssss"
end
def regMailUsr(nazov, priezvisko, email, pass)
#nazov = nazov
#priezvisko = priezvisko
#email = email
#pass = pass
#url = "http://loyalty2.xxxx.sk"
mail(to: email, subject: 'Vaša registrácia bola dokončená.')
end
end
I have also created View for this mail controller but that is not important right now.
The fragments from clients controller are here:
/app/controllers/clients_controller.rb
# encoding: UTF-8
class ClientsController < ApplicationController
load_and_authorize_resource
.......
def new
#noveHeslo = genHeslo(10) # << I defined this in application_controller.rb and it works
UserMailer.kokotina # << just a dummy method from UserMailer
#client = Client.new(params[:client])
.......
end
.......
def create
.......
if #client.save
#send email to new client:
UserMailer.regMailUsr(params[:client][:fname], params[:client][:lname], params[:client][:email], params[:client][:password]).deliver
.....
end ......
Now how do I know that my class is loaded? If in client controller, I change 'UserMailer' to 'xUserMailer', I will get 'no class or method in ...' error, but without 'x', I get only:
'undefined method `kokotina' for UserMailer:Class'
I also tried to define my methods in UserMailer:Class like this:
def self.kokotina # << this is just a dummy method for testing
caf = "ssss"
end
#or even like this
def self <<
def kokotina # << this is just a dummy method for testing
caf = "ssss"
end
end
#and then tried to invoke this method(s) like this:
UserMailer.new.kokotina
#or simply
kokotina
Strange is, that when I put contents of file '/app/mailers/user_mailer.rb' at the end of 'application_helper.rb' file, just after the end of 'module ApplicationHelper', I get no errors but of course, it won`t work.
Please keep in mind that I have no problem coding in another languages, but this mystic/kryptic rules of Ruby on Rails are still a complete mistery to me and unfortunatelly, I don`t have time or even motivation to read time extensive quides or even books for RoR beginners. I have been coding much more difficult applications and implementations, but this heavily discriminating system is driving me nuts.
Thank you all!
Problem solved!
The trick was, that in '/app/mailers/user_mailer.rb', I had multibyte characters. In mail subject.
So I added:
# encoding: UTF-8
at the very first line of '/app/mailers/user_mailer.rb'
I found this by total accident: later my rails app could not start, and server was simply throwing HTTP 500 error. So no trace, error defining etc.
I found out that multibyte string in:
mail(to: email, subject: 'Vaša registrácia bola dokončená.')
Was responsible for crash. When I removed that string, I noticed one important side effect: my methods became magicaly available for another controller!!!!
So if someone could give me at least one reason to lowe Rails...
I'm porting to scala a java application I wrote as a learning exercise. Part of it is communicating with another machine using a protocol directly over TCP. This protocol has 2 layers (Application and Transport) with corresponding headers and trailers. So a given message would end looking like
tHeader | aHeader | message | aTrailer | tTrailer
I thought of using a
trait Layer{
def write(s:String) : Unit
def read :String
}
Each layer would complete the message with its header|trailer and pass it to the next layer. I would then have a TransportLayer and a ApplicationLayer which I could combine in an implementation like
val layer = new TcpLayer with ApplicationLayer with TransportLayer
I was thrilled with this design, it allowed me to reuse the components at will. And then my dilemma appeared:
In order to avoid side effects, the write method should not return Unit,but the modified String, which should then be sent. This minimizes the side effects, eases testing, but the client code would need to send the completed String over the socket itself ( which already has a side effect but no way around that one, or?).
Since the client code should 'fire and forget', it should be able to call write on a given Layer and don't care about the modified String ( which might be gibberish to him anyway) . Therefore I think to return Unit is the correct choice for the return value.
Any thoughtful insights on which version is more functional programming - friendly?
If the only side-effect is in the socket, then how about this:
val layers = new TcpLayer with ApplicationLayer with TransportLayer
socket.write (layers.write (message))
unitTestEngine.check (layers.write (message))
Or if the client only have the layer to work with, then:
val layers = new TcpLayer (socket) with ApplicationLayer with TransportLayer
layers.sink (layers.write (message))
unitTestEngine.check (layers.write (message))
Where sink is a method returning the Socket instance.
For a project, I have a complex master object that contains a number of subcomponents. Set up of these objects is controlled by a Constructor interface, which I bind to various lifecycle & workflow events, like so:
#grok.subscribe(schema.ICustomFolder, lifecycleevent.IObjectAddedEvent)
def setup_custom_folder(folder, event):
interfaces.IConstructor(folder).setup()
#grok.subscribe(schema.ICustomFolder, lifecycleevent.IObjectModifiedEvent)
def setup_custom_folder(folder, event):
interfaces.IConstructor(folder).update()
What I'd like to be able to do is test the Constructor methods without relying on the event handlers. I've tried doing this by creating objects directly to avoid the lifecycle events:
def test_custom_item_constructor(self):
master = createContent('model.master_object',
needed_attribute = 2
)
folder = createContent('model.custom_folder',
__parent__ = master
)
self.assertEqual(0, len(folder))
constructor = interfaces.IConstructor(folder)
constructor.setup()
self.assertEqual(2, len(folder))
The setup method creates a number of items inside the Custom_Folder instance, dependent on the provided attribute on the master object. However, this is hanging, which I think is due to neither object actually belonging to the site, so there's no acquisition of permissions. I can get this by changing the createContent on the master object to createContentInContainer and adding it to the appropriate part of the test site, but that triggers all of the lifecycle events, which end up doing the Constructor calls, which doesn't let me test them in isolation.
I've tried using mock objects for this, but that got messy dealing with the content creation that is meant to occur during the Constructor .setup.
What's the best way to approach this?
I'm not sure if this is the best way, but I managed to get the result I wanted by disabling the relevant event handlers first, and then creating the content properly within the site:
def test_custom_item_constructor(self):
zope.component.getGlobalSiteManager().unregisterHandler(
adapters.master.constructor.setup_masterobject,
required=[schema.IMasterObject, lifecycleevent.IObjectAddedEvent]
)
zope.component.getGlobalSiteManager().unregisterHandler(
adapters.custom.constructor.setup_customfolder,
required=[schema.ICustomFolder, lifecycleevent.IObjectAddedEvent]
)
master = createContentInContainer(self.portal, 'model.master_object',
needed_attribute = 2
)
folder = createContentInContainer(master, 'model.custom_folder',
__parent__ = master
)
self.assertEqual(0, len(folder))
constructor = interfaces.IConstructor(folder)
constructor.setup()
self.assertEqual(2, len(folder))
This was enough to disengage the chain of events triggered by the addition of a new master object.
I've got the following model
class User < ActiveRecord::Base
before_create :set_some_values
private
def set_some_values
#do something
end
end
In specs I'm using Fabrication gem to create objects but I can't find a way to stub the set_some_values method. I tried
User.any_instance.stub!(:set_some_values).and_return(nil)
but Fabrication seems to ignore this. Is it possible to do?
This is why I don't like ActiveRecord callbacks -- because if you want to have nothing to do with a callback (because, say, you're making a call to an external service inside the callback) you still have to be concerned about stubbing it out. Yes you could stub out methods inside the callback, but it's the same problem, and actually it's a bit worse because now you are concerned about something inside a method you want nothing to do with.
As usual there are multiple options here.
One option which I've used a lot in the past is, add a condition to your callback that turns it off by default. So your Post class could look like:
class Post
before_save :sync_with_store, :if => :syncing_with_store?
def syncing_with_store?; #syncing_with_store; end
attr_writer :syncing_with_store
def sync_with_store
# make an HTTP request or something
end
end
Now wherever you really want to call the callback (perhaps in your controller or wherever), you can set post.syncing_with_store = true before you call post.save.
The downside to this approach is, it's something that you (and other devs working with you) have to keep in mind, and it's not really obvious that you have to do this. On the other hand, if you forget to do this, nothing bad happens.
Another option is to use a fake class. Say you have a Post that pushes its data to an external data store on save. You could extract the code that does the pushing to a separate class (e.g. Pusher) which would be accessible at Post.pusher_service. By default, though, this would be set to a fake Pusher class that responds to the same interface but does nothing. So like:
class Post
class << self
attr_accessor :pusher_service
end
self.pusher_service = FakePostPusher
before_save :sync_with_store
def sync_with_store
self.class.pusher_service.run(self)
end
end
class FakePostPusher
def self.run(post)
new(post).run
end
def initialize(post)
#post = post
end
def run
# do nothing
end
end
class PostPusher < FakePostPusher
def run
# actually make the HTTP request or whatever
end
end
In your production environment file, you'd set Post.pusher_service = Pusher. In individual tests or test cases, you'd make a subclass of Post -- let(:klass) { Class.new(Post) } -- and set klass.pusher_service = Pusher (that way you don't permanently set it and affect future tests).
The third approach, which I have been experimenting with, is this: simply don't use ActiveRecord callbacks. This is something I picked up from Gary Bernhardt's screencasts (which, by the way, are pretty amazing). Instead, define a service class that wraps the act of creating a post. Something like:
class PostCreator
def self.run(attrs={})
new(attrs).run
end
def initialize(attrs={})
#post = Post.new(attrs)
end
def run
if #post.save
make_http_request
return true
else
return false
end
end
def make_http_request
# ...
end
end
This way PostCreator.run(attrs) is the de facto way of creating a post instead of going through Post. Now to test saves within Post, there's no need to stub out callbacks. If you want to test the PostCreator process, there's no magic going on, you can easily stub out whichever methods you want or test them independently. (You could argue that stubbing out methods here is the same as stubbing out AR callbacks, but I think it's more explicit what's going on.) Obviously this only handles post creation, but you could do the same for post updating too.
Anyway, different ideas, pick your poison.
The #set_some_values method here is called when you call #save on the record. So it has nothing to do with the constructor and therefore you don't need to stub User.any_instance -- just make your record and then do a partial stub, as in:
record.stub(:set_some_values)
record.save
Having a problem with making this work. Seems it has been a problem for others and I think I have followed all of the advice.
I've set up a stripped down rails 3 .0.14 app to just include cucumber-rails & factory_girl_rails but still no go. I expect I am doing something silly!
Running the cuc test below produces the following:
Scenario: test factory-girl # features/users.feature:3
Given the following user exists: # features/users.feature:4
| name | email |
| Brandon | brandon#example.com |
Undefined step: "the following user exists:" (Cucumber::Undefined)
The user factory has been created, of which I am sure, with a bit of 'pp' output.
Would really appreciate any help to get this sorted.
Ross
Set up
env.rb: snippet
require 'pp'
require 'cucumber/rails'
require 'factory_girl_rails'
require 'factory_girl/step_definitions'
features/support/factories.rb:
FactoryGirl.define do
factory :user do
name 'Adam Advertiser'
email 'a#b.com'still
end
end
pp FactoryGirl.create(:user)
Cucumber features/user.feature:
Feature: a
Scenario: test factory-girl
Given the following user exists:
| name | email |
| Brandon | brandon#example.com |
The problem here is that you have to get your factory required before requiring 'factory_girl/step_definitions', because there is meta-programming in step_definitions which needs to know about your factory. You could explicitly require the factories.rb in the env.rb, but that will end up producing a duplicate definition error, as cucumber will re-require factories.rb.
You need to remove the requiring of step_definitions from the env.rb - that will make it happen too early - and put it at the bottom of factories.rb, or else create a wrapper which requires first the factories (which will need to reside somewhere that cucumber doesn't automatically require) and then the step_definitions.