Add couchbase with modifications, formalize broadcaster and make testable and some other stuff

This commit is contained in:
2014-03-06 18:08:39 +01:00
parent 3f117c76b0
commit 0e7a39b819
28 changed files with 456 additions and 35 deletions
+5
View File
@@ -34,6 +34,11 @@ group :assets do
end
#gem 'less-rails'
# COUCHBASE GEMS
gem 'couchbase'
gem 'map'
gem 'couchbase-docstore'
gem 'couchbase-structures', github: 'bterkuile/couchbase-structures'
gem 'couch_potato' , github: 'bterkuile/couch_potato'
gem 'simply_stored', path: './../components/simply_stored' #, github: 'bterkuile/simply_stored'
+25 -1
View File
@@ -7,6 +7,15 @@ GIT
couchrest (>= 1.0.1)
json (~> 1.6)
GIT
remote: git://github.com/bterkuile/couchbase-structures.git
revision: f6f7da685fd8bacd2bd05b55eac73e8b82a8078d
specs:
couchbase-structures (0.1.0)
couchbase (>= 1.2.0.z.beta3)
couchbase-docstore (>= 0.1.2)
couchbase-settings (>= 0.1.0)
GIT
remote: git://github.com/bterkuile/devise_simply_stored.git
revision: 8fdaed396a7e9566fe6cb59b64d950953c8506a6
@@ -120,7 +129,16 @@ GEM
sass (~> 3.1)
compass-rails (1.1.2)
compass (>= 0.12.2)
connection_pool (1.2.0)
cookiejar (0.3.0)
couchbase (1.3.6)
connection_pool (~> 1.0, >= 1.0.0)
multi_json (~> 1.0)
yaji (~> 0.3, >= 0.3.2)
couchbase-docstore (0.3.0)
couchbase (>= 1.2)
couchbase-settings (>= 0.2.7)
couchbase-settings (0.3.4)
couchrest (1.2.0)
mime-types (~> 1.15)
multi_json (~> 1.0)
@@ -221,13 +239,14 @@ GEM
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
map (6.5.3)
method_source (0.8.2)
mime-types (1.25.1)
mini_magick (3.7.0)
subexec (~> 0.2.1)
mini_portile (0.5.2)
minitest (4.7.5)
multi_json (1.8.2)
multi_json (1.9.0)
multipart-post (1.2.0)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
@@ -353,6 +372,7 @@ GEM
websocket-driver (0.3.1)
xpath (2.0.0)
nokogiri (~> 1.3)
yaji (0.3.5)
PLATFORMS
ruby
@@ -367,6 +387,9 @@ DEPENDENCIES
coffee-rails
compass-rails
couch_potato!
couchbase
couchbase-docstore
couchbase-structures!
database_cleaner
devise
devise_simply_stored!
@@ -382,6 +405,7 @@ DEPENDENCIES
kaminari
launchy
letter_opener
map
mini_magick
omniauth-facebook
orm_adapter!
+2 -13
View File
@@ -12,15 +12,11 @@ private
end
def broadcast_user(uid, event, data = {})
message = {channel: "/user/#{uid}", data: {event: event, data: data}}
uri = URI.parse(event_host)
Net::HTTP.post_form(uri, :message => message.to_json)
Qwaiter.broadcast_user uid, event, data
end
def broadcast_supplier(sid, event, data = {})
message = {channel: "/supplier/#{sid}", data: {event: event, data: data}}
uri = URI.parse(event_host)
Net::HTTP.post_form(uri, :message => message.to_json)
Qwaiter.broadcast_supplier sid, event, data
end
def set_locale
@@ -64,13 +60,6 @@ private
end
alias json_notice js_notice
# Return the hostname of the event server
def event_host
#TODO: do not do and environment check, its ugly!
"http://#{Rails.env.production? ? 'events.qwaiter.com' : 'localhost'}:9296/faye"
end
helper_method :event_host
def show_404
respond_to do |format|
format.html { render 'dashboard/404', layout: true, status: 404}
+3
View File
@@ -270,6 +270,7 @@ class List
return false unless user
order = Order.create list: self, supplier: supplier, user: user, section_id: section_id
return unless order.id
orders_in_process_count = supplier.increment_orders_in_process_count!
loaded_products = self.class.database.load_document products.keys
products.each do |product_id, quantity|
quantity = quantity.to_i
@@ -280,9 +281,11 @@ class List
save
for user_id in user_ids
broadcast_user user_id, 'new_order', order: order.with_products_as_json, total_amount: price
broadcast_user user_id, 'orders_in_process_count', count: orders_in_process_count
end
broadcast_supplier supplier.id, 'list_update', active_model_serializer.new(self).as_json
broadcast_supplier supplier.id, 'new_order', OrderSerializer.new(order)
broadcast_supplier supplier.id, 'orders_in_process_count', count: orders_in_process_count
order
end
+9
View File
@@ -77,20 +77,29 @@ class Order
def is_being_processed!
self.state = 'active'
if save
orders_in_process_count = supplier.decrement_orders_in_process_count!
orders_delivered_count = supplier.increment_orders_delivered_count!
for user_id in list.user_ids
broadcast_user user_id, 'order_being_processed', id: id, list_id: list_id
broadcast_user user_id, 'orders_in_process_count', count: orders_in_process_count
broadcast_user user_id, 'orders_delivered_count', count: orders_delivered_count
end
broadcast_supplier supplier_id, 'order_being_processed', id: id, list_id: list_id
broadcast_supplier supplier_id, 'orders_in_process_count', count: orders_in_process_count
broadcast_supplier supplier_id, 'orders_delivered_count', count: orders_delivered_count
end
end
def is_delivered!
self.state = 'delivered'
if save
orders_delivered_count = supplier.decrement_orders_delivered_count!
for user_id in list.user_ids
broadcast_user user_id, 'order_being_delivered', id: id, list_id: list_id
broadcast_user user_id, 'orders_delivered_count', count: orders_delivered_count
end
broadcast_supplier supplier_id, 'order_being_delivered', id: id, list_id: list_id
broadcast_supplier supplier_id, 'orders_delivered_count', count: orders_delivered_count
end
end
+29
View File
@@ -137,6 +137,35 @@ class Supplier
def week_starts_on_monday?
true
end
def increment_orders_in_process_count!
Qwaiter::Counter.incr "supplier:#{id}:orders_in_process"
end
def increment_orders_delivered_count!
Qwaiter::Counter.incr "supplier:#{id}:orders_delivered"
end
def decrement_orders_in_process_count!
Qwaiter::Counter.decr "supplier:#{id}:orders_in_process"
end
def decrement_orders_delivered_count!
Qwaiter::Counter.decr "supplier:#{id}:orders_delivered"
end
def orders_in_process_count
Qwaiter::Counter.get "supplier:#{id}:orders_in_process"
end
def orders_delivered_count
Qwaiter::Counter.get "supplier:#{id}:orders_delivered"
end
def active_order_count
orders_in_process_count + orders_delivered_count
end
private
def add_section_on_create
+2 -2
View File
@@ -20,14 +20,14 @@ html lang="en"
javascript:
var QMobile, Qwaiter, Quser;
var data_host = 'http://data.qwaiter.com';
var event_host = '#{event_host}';
var event_host = '#{Qwaiter.event_host}';
var $asset_path = '##assets_path##';
var Qstorage = localStorage;
- else
javascript:
var QMobile, Qwaiter, Quser;
var data_host = 'http://data.qwaiter.com';
var event_host = '#{event_host}';
var event_host = '#{Qwaiter.event_host}';
var $asset_path = '/assets/';
var Qstorage = localStorage;
#{user_dynamic_data_host}
@@ -2,6 +2,6 @@
var $locale = '<%= I18n.locale %>';
var supplier_id = '<%= current_supplier.id %>';
var data_host = '';
var event_host = '<%= event_host %>';
var event_host = '<%= Qwaiter.event_host %>';
var datepicker_options = {dateFormat: 'yy-mm-dd', firstDay: <%= current_supplier.week_starts_on_monday? ? 1 : 0 %>};
</script>
+3
View File
@@ -9,6 +9,9 @@ require 'action_mailer/railtie'
require 'rails/test_unit/railtie'
require 'sprockets/railtie'
# custom override hack for the couchbase-setting gem, needs to be loaded before other gems, is settings only without dependencies
require File.expand_path('./../../lib/couchbase-setting', __FILE__)
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require(*Rails.groups(:assets => %w(development test)))
+15
View File
@@ -0,0 +1,15 @@
defaults: &defaults
server: 127.0.0.1
development:
<<: *defaults
bucket: qwaiter_development
test:
<<: *defaults
bucket: qwaiter_test
production:
<<: *defaults
bucket: qwaiter
+1
View File
@@ -0,0 +1 @@
+2 -1
View File
@@ -1,3 +1,4 @@
#TODO: this is really ugly, can cause memory leaks and much more bad stuff. We need a new broadcaster....
require 'simply_stored/couch'
module ModelBroadcast
def broadcast_supplier(*args)
@@ -8,4 +9,4 @@ module ModelBroadcast
end
end
SimplyStored::Couch.send(:include, ModelBroadcast)
SimplyStored::Couch.send(:extend, ModelBroadcast)
#SimplyStored::Couch.send(:extend, ModelBroadcast) # this should never happen!!!
+2
View File
@@ -0,0 +1,2 @@
Qwaiter.event_host = "http://#{Rails.env.production? ? 'events.qwaiter.com' : 'localhost'}:9296/faye"
Qwaiter.broadcaster = Qwaiter::Broadcaster::Faye.new
+44
View File
@@ -0,0 +1,44 @@
# This is a replacement of the couchbase settings gem. The quality of this gem is not sufficient to
# work with. Since couchbase structures/docstore are using CouchbaseSetting we supply the right settings
# here
module CouchbaseSetting
mattr_accessor :config_type
mattr_accessor :configs
def self.server
config :server, default: '127.0.0.1'
end
def self.bucket
config :bucket, default: 'qwaiter'
end
def self.config(setting, default: nil)
configs[config_type][Rails.env.to_sym][setting] || default
end
end
CouchbaseSetting.configs = {
supplier_counters: {
development: {
bucket: 'qwaiter_development'
},
test:{
bucket: 'qwaiter_development'
},
production: {}
},
queue: {
development: {
bucket: 'qwaiter_development'
},
test:{
bucket: 'qwaiter_development'
},
production: {}
}
}
# only one in use at the moment
CouchbaseSetting.config_type = :supplier_counters
+3
View File
@@ -0,0 +1,3 @@
# this file is for backwards compatibility of the couchbase-structures gem since version 0.1.0 requires this file
# while couchbase-docstore is already over to the new require 'couchbase-docstore' syntax (kinda)
require 'couchbase-docstore'
+3
View File
@@ -0,0 +1,3 @@
# this file is for backwards compatibility of the couchbase-structures gem since version 0.1.0 requires this file
# while couchbase-settings is custom for now
require 'couchbase-setting'
+3
View File
@@ -0,0 +1,3 @@
# this file is for backwards compatibility of the couchbase-structures gem since version 0.1.0 requires this file
# while couchbase-settings is custom for now
require 'couchbase-setting'
+14
View File
@@ -1,6 +1,20 @@
module Qwaiter
mattr_accessor :event_host
mattr_accessor :broadcaster
extend ActiveSupport::Autoload
autoload :Distribution
autoload :Serializer
autoload :Counter
autoload :Broadcaster
def self.broadcast_user(uid, event, data)
message = {channel: "/user/#{uid}", data: {event: event, data: data}}
broadcaster.broadcast message
end
def self.broadcast_supplier(sid, event, data)
message = {channel: "/supplier/#{sid}", data: {event: event, data: data}}
broadcaster.broadcast message
end
end
+6
View File
@@ -0,0 +1,6 @@
module Qwaiter
module Broadcaster
extend ActiveSupport::Autoload
autoload :Faye
end
end
+10
View File
@@ -0,0 +1,10 @@
module Qwaiter
module Broadcaster
class Faye
def broadcast(message)
@uri ||= URI.parse(Qwaiter.event_host)
Net::HTTP.post_form(@uri, :message => message.to_json)
end
end
end
end
+27
View File
@@ -0,0 +1,27 @@
module Qwaiter
module Counter
mattr_accessor :connection
# mainly for testing purposes
def self.set(key, value)
connection.set(key, value)
end
def self.get(key)
connection.get(key).to_i
end
def self.incr(*args)
connection.incr(*args)
end
def self.decr(*args)
connection.decr(*args)
end
end
end
# use the connection from couchbase-structures/documents
# will be overwritten in the specs since flushing the real
# thing is difficult
Qwaiter::Counter.connection = $cb
+32
View File
@@ -122,4 +122,36 @@ describe List do
end
end
describe '#place_order' do
it 'returns an order object' do
list.place_order(user, product.id => 7).should be_a Order
end
it 'creates an order' do
expect{ list.place_order(user, product.id => 7) }.to change{ Order.count }.by(1)
end
describe 'broadcasting' do
it 'broadcasts to the user and the supplier the active order counter' do
list.place_order(user, product.id => 7)
expect{
list.place_order(user, product.id => 3)
}.to broadcast_to_user(user.id).message('orders_in_process_count').with(count: 2)
expect{
list.place_order(user, product.id => 5)
}.to broadcast_to_supplier(supplier.id).message('orders_in_process_count').with(count: 3)
end
end
it 'sets the list price as kind of caching' do
list.place_order(user, product.id => 7)
list.reload
list.price.should == 3
end
describe 'product order creation'
end
end
+60
View File
@@ -37,4 +37,64 @@ describe Order do
it 'paginates'
end
describe '#is_being_processed!' do
describe 'broadcasting' do
it 'broadcasts order info to the user' do
expect{ order.is_being_processed! }.to broadcast_to_user(user.id).message( 'order_being_processed' ).with(id: order.id, list_id: list.id)
end
describe 'counters' do
before do
# hack some initial values
Qwaiter::Counter.set "supplier:#{supplier.id}:orders_in_process", 7
Qwaiter::Counter.set "supplier:#{supplier.id}:orders_delivered", 9
end
it 'reduces the orders_in_process count and communicates it to user' do
expect{ order.is_being_processed! }.to broadcast_to_user(user.id).message( 'orders_in_process_count' ).with(count: 6)
end
it 'increases the orders_delivered count and communicates it to user' do
expect{ order.is_being_processed! }.to broadcast_to_user(user.id).message( 'orders_delivered_count' ).with(count: 10)
end
it 'reduces the orders_in_process count and communicates it to supplier' do
expect{ order.is_being_processed! }.to broadcast_to_supplier(supplier.id).message( 'orders_in_process_count' ).with(count: 6)
end
it 'increases the orders_in_process count and communicates it to supplier' do
expect{ order.is_being_processed! }.to broadcast_to_supplier(supplier.id).message( 'orders_delivered_count' ).with(count: 10)
end
end
it 'broadcasts order info to the supplier' do
expect{ order.is_being_processed! }.to broadcast_to_supplier(supplier.id).message( 'order_being_processed' ).with(id: order.id, list_id: list.id)
end
end
end
describe '#is_delivered!' do
describe 'broadcasting' do
describe 'counters' do
before do
# hack some initial values
Qwaiter::Counter.set "supplier:#{supplier.id}:orders_in_process", 7
Qwaiter::Counter.set "supplier:#{supplier.id}:orders_delivered", 9
end
it 'decreases the orders_delivered count and communicates it to user' do
expect{ order.is_delivered! }.to broadcast_to_user(user.id).message( 'orders_delivered_count' ).with(count: 8)
end
it 'decreases the orders_in_process count and communicates it to supplier' do
expect{ order.is_delivered! }.to broadcast_to_supplier(supplier.id).message( 'orders_delivered_count' ).with(count: 8)
end
end
end
end
end
+22 -17
View File
@@ -1,62 +1,67 @@
require 'spec_helper'
describe Supplier do
before :each do
@supplier = build :supplier
end
let(:supplier){ build :supplier }
# property open
describe :open do
it 'should be false by default' do
@supplier.open.should == false
supplier.open.should == false
end
it 'should not be open? by default' do
@supplier.open?.should == false
supplier.open?.should == false
end
it 'should be closed? by default' do
@supplier.closed?.should == true
supplier.closed?.should == true
end
describe :mark_as_open! do
before :each do
@supplier.mark_as_open!
supplier.mark_as_open!
end
it 'should be persisted in the database' do
@supplier.reload
@supplier.open.should == true
supplier.reload
supplier.open.should == true
end
it 'should be open?' do
@supplier.open?.should == true
supplier.open?.should == true
end
it 'should not be closed?' do
@supplier.closed?.should == false
supplier.closed?.should == false
end
end
describe :mark_as_closed! do
before :each do
@supplier.mark_as_open!
@supplier.mark_as_closed!
supplier.mark_as_open!
supplier.mark_as_closed!
end
it 'should be persisted in the database' do
@supplier.reload
@supplier.open.should == false
supplier.reload
supplier.open.should == false
end
it 'should be open?' do
@supplier.open?.should == false
supplier.open?.should == false
end
it 'should not be closed?' do
@supplier.closed?.should == true
supplier.closed?.should == true
end
end
end
describe '#decrement_orders_delivered_count!' do
it 'decreases orders_delivered' do
Qwaiter::Counter.set "supplier:#{supplier.id}:orders_delivered", 9
supplier.decrement_orders_delivered_count!.should == 8
end
end
end
+6
View File
@@ -43,6 +43,10 @@ module SpecSelectorHelpers
%x(launchy http://localhost:3000/capybara.html)
end
end
# NOT THREADSAFE!!!!!! but good enough for testing since the real couchbase flush is slowwwwww....
Qwaiter::Counter.connection = InMemoryQCounter.new
RSpec.configure do |config|
# == Mock Framework
#
@@ -56,6 +60,7 @@ RSpec.configure do |config|
config.include FactoryAttributesFor
config.include Devise::TestHelpers, type: :controller
config.include EndWithMatcher
config.include Matchers
config.include Features::BasicHelpers, type: :feature
config.include SpecRouteHelpers, type: :feature
#config.use_transactional_fixtures = true
@@ -98,6 +103,7 @@ RSpec.configure do |config|
config.before :each do
CouchPotato.couchrest_database.recreate!
Qwaiter::Counter.connection.flush
end
config.before :each, type: :feature do
+29
View File
@@ -0,0 +1,29 @@
# This is a non thread safe replacement for the
# couchbase counter mechanism since every test needs
# a clean start and Hash#clear is soooo much faster than
# a couchbase bucket flush
class InMemoryQCounter
STORE = {}
def get(key)
STORE[key]
end
def set(key, value)
STORE[key] = value
end
def incr(key, options = {})
STORE[key] ||= options[:initial].to_i
STORE[key] += 1
end
def decr(key, options = {})
STORE[key] ||= options[:initial].to_i
STORE[key] -= 1
end
def flush
STORE.clear
end
end
@@ -0,0 +1,49 @@
module Matchers
class BroadcastToSupplier
class TestBroadcaster
attr_reader :broadcasts
def initialize
@broadcasts = []
end
def broadcast(object)
self.broadcasts << object
end
end
def initialize(supplier_id)
@supplier_id = supplier_id
end
def matches?(block)
old_broadcaster = Qwaiter.broadcaster
test_broadcaster = TestBroadcaster.new
Qwaiter.broadcaster = test_broadcaster
block.call
Qwaiter.broadcaster = old_broadcaster
relevant_broadcasts = test_broadcaster.broadcasts.select{|b| b[:channel] =~ /^\/supplier\/#{@supplier_id}/ && b[:data][:event] == @message}
@failure_debug_content = "was #{relevant_broadcasts.map{|b| b[:data][:data].inspect}.join(" and ")}"
relevant_broadcasts.any?{|b| b[:data][:data] == @target_object}
end
def message(msg)
@message = msg
self
end
def with(target_object)
@target_object = target_object
self
end
def failure_message
"supplier #{@supplier_id} did not receive broadcast #{@message} with #{@target_object.inspect} #{@failure_debug_content}"
end
end
def broadcast_to_supplier(*args, &block)
BroadcastToSupplier.new(*args, &block)
end
end
@@ -0,0 +1,49 @@
module Matchers
class BroadcastToUser
class TestBroadcaster
attr_reader :broadcasts
def initialize
@broadcasts = []
end
def broadcast(object)
self.broadcasts << object
end
end
def initialize(user_id)
@user_id = user_id
end
def matches?(block)
old_broadcaster = Qwaiter.broadcaster
test_broadcaster = TestBroadcaster.new
Qwaiter.broadcaster = test_broadcaster
block.call
Qwaiter.broadcaster = old_broadcaster
relevant_broadcasts = test_broadcaster.broadcasts.select{|b| b[:channel] =~ /^\/user\/#{@user_id}/ && b[:data][:event] == @message}
@failure_debug_content = "was #{relevant_broadcasts.map{|b| b[:data][:data].inspect}.join(" and ")}"
relevant_broadcasts.any?{|b| b[:data][:data] == @target_object}
end
def message(msg)
@message = msg
self
end
def with(target_object)
@target_object = target_object
self
end
def failure_message
"user #{@user_id} did not receive broadcast #{@message} with #{@target_object.inspect} #{@failure_debug_content}"
end
end
def broadcast_to_user(*args, &block)
BroadcastToUser.new(*args, &block)
end
end