From 99a9536c684c912173ad9fe83b476f3f4ca7cfe4 Mon Sep 17 00:00:00 2001 From: Benjamin ter Kuile Date: Tue, 5 Aug 2014 17:49:16 +0200 Subject: [PATCH] Replace couchbase counters with drb version --- Gemfile | 8 +- Gemfile.lock | 24 ---- Procfile | 5 +- app/models/list.rb | 110 +-------------- app/models/list/depricated_serialization.rb | 49 +++++++ app/models/list/join_requests.rb | 51 +++++++ app/models/supplier.rb | 72 +--------- app/models/supplier/counters.rb | 84 ++++++++++++ bin/drb_counter.rb | 13 ++ config/initializers/qwaiter_settings.rb | 8 ++ {spec/support => lib}/in_memory_q_counter.rb | 8 -- lib/qwaiter.rb | 2 +- lib/qwaiter/couchbase.rb | 7 + lib/qwaiter/counter.rb | 5 - lib/qwaiter/drb_counter.rb | 8 ++ spec/models/list/join_requests_spec.rb | 129 ++++++++++++++++++ spec/models/list_spec.rb | 42 +----- spec/models/supplier_spec.rb | 24 ++-- spec/spec_helper.rb | 27 ++-- .../matchers/broadcast_to_user_matcher.rb | 6 +- 20 files changed, 400 insertions(+), 282 deletions(-) create mode 100644 app/models/list/depricated_serialization.rb create mode 100644 app/models/list/join_requests.rb create mode 100644 app/models/supplier/counters.rb create mode 100755 bin/drb_counter.rb rename {spec/support => lib}/in_memory_q_counter.rb (80%) create mode 100644 lib/qwaiter/drb_counter.rb create mode 100644 spec/models/list/join_requests_spec.rb diff --git a/Gemfile b/Gemfile index 6782b2a1..61c5221a 100644 --- a/Gemfile +++ b/Gemfile @@ -41,10 +41,10 @@ end #gem 'less-rails' # COUCHBASE GEMS -gem 'couchbase' -gem 'map' -gem 'couchbase-docstore' -gem 'couchbase-structures', github: 'bterkuile/couchbase-structures' +# 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' diff --git a/Gemfile.lock b/Gemfile.lock index fbd64aa3..a2355be4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,15 +19,6 @@ 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: 86f93782c607958e7f99a6fdbae5566e17047273 @@ -127,16 +118,7 @@ GEM execjs coffee-script-source (1.7.1) colorize (0.7.3) - connection_pool (2.0.0) cookiejar (0.3.2) - couchbase (1.3.8) - connection_pool (>= 1.0.0, <= 3.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) @@ -242,7 +224,6 @@ GEM mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - map (6.5.4) method_source (0.8.2) mime-types (1.25.1) mini_magick (3.7.0) @@ -404,7 +385,6 @@ GEM websocket-driver (0.3.4) xpath (2.0.0) nokogiri (~> 1.3) - yaji (0.3.5) PLATFORMS ruby @@ -422,9 +402,6 @@ DEPENDENCIES cmtool! coffee-rails couch_potato! - couchbase - couchbase-docstore - couchbase-structures! database_cleaner devise devise_simply_stored! @@ -443,7 +420,6 @@ DEPENDENCIES kaminari launchy letter_opener - map mini_magick omniauth-facebook paperclip diff --git a/Procfile b/Procfile index 92f24260..263ac329 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,4 @@ -web: bundle exec thin start -p $PORT -faye: thin start -R faye/config.ru -p 9296 +#web: bundle exec thin start -p $PORT +faye: thin start -R faye/config.ru -p 9296 #faye: rackup faye.ru -s thin -E production +counters: bin/drb_counter.rb start diff --git a/app/models/list.rb b/app/models/list.rb index bfe9361d..61763bc3 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -1,13 +1,13 @@ class List include SimplyStored::Couch include ActiveModel::SerializerSupport + include List::JoinRequests per_page_method :limit_value #kaminari property :state, default: 'active' # active, #closed property :needs_help, type: :boolean, default: false property :needs_payment, type: :boolean, default: false property :closed_at, type: Time - property :join_request_user_ids, type: Array, default: [] property :price, type: Float property :is_paid, type: :boolean, default: false property :paid_at, type: Time @@ -139,9 +139,10 @@ class List def close! orders.include_relation(:product_orders) - set_price - orders.map(&:close!) + set_price # should not be needed, but extra secure + orders.map(&:close!) # close the connected orders self.state = 'closed' + self.user_requests_closing = false # if a user requested closing, not needed anymore self.closed_at = Time.now if save for user in users @@ -187,6 +188,8 @@ class List end end + # This method is called when a user of the list wants the list + # actively to be closed def user_requests_closing! return if user_requests_closing? self.user_requests_closing = true @@ -216,18 +219,6 @@ class List end end - def approve_join_request_for_user!(user) - if join_request_user_ids.include?(user.id) - join_request_user_ids.delete(user.id) - user.active_list_id = self.id - add_user(user) - user.save - self.is_dirty - if save - broadcast_user user.id, 'join_request_approved', UserUserSerializer.new(user).as_json - end - end - end def unlink_user(user) changed = join_request_user_ids.delete(user.id) @@ -239,36 +230,6 @@ class List save if changed end - def send_table_join_request_for_user!(requester) - unless join_request_user_ids.include?(requester.id) or user_ids.include?(requester.id) - @join_requests = nil # bust cache - self.join_request_user_ids << requester.id - self.is_dirty - if save - broadcast_users 'user_join_request', JoinRequestSerializer.new(join_request_for_user(requester)).as_json - end - end - end - - def join_request_for_user(user) - JoinRequest.new(user: user, list: self) - end - - - def join_requests - @join_requests ||= join_request_user_ids.any? ? self.class.database.load_document(join_request_user_ids).map{|user| join_request_for_user(user) } : [] - end - - def reject_join_request_for_user!(user_id) - if join_request_user_ids.include?(user_id) - join_request_user_ids.delete(user_id) - self.is_dirty - if save - broadcast_user user_id, 'join_request_rejected', id: "jr-#{user_id}" - end - end - end - def relevant_orders orders.reject(&:cancelled?) end @@ -319,50 +280,6 @@ class List order end - def as_json(*args) - super.merge(id: id, table_number: table_number, has_active_orders: has_active_orders? ) - end - - def with_orders_as_json - return @with_orders_as_json if @with_orders_as_json.present? - orders.include_relations(product_orders: :product) - h = as_json - h[:orders] = [] - h[:list_active] = active? - list_total = 0.0 - for order in orders - ho = {products: []} - ho[:id] = order.id - ho[:state] = order.state - order_total = 0.0 - for product_order in order.product_orders - order_total += (product_order.quantity * product_order.price).round(2) - ho[:products] << {name: product_order.product_name, id: product_order.product_id, number: product_order.quantity, price: product_order.price} - end - ho[:total_amount] = order_total.round(2) - ho[:state] = order.state - list_total += ho[:total_amount] - h[:orders] << ho - end - h[:total_amount] = list_total.round(2) - @with_orders_as_json = h - end - - def with_orders_and_join_requests_as_json - return @with_orders_and_join_requests_as_json if @with_orders_and_join_requests_as_json.present? - @with_orders_and_join_requests_as_json = with_orders_as_json.merge(join_requests_as_json) - end - - def with_orders_and_join_requests_and_supplier_info_as_json - with_orders_and_join_requests_as_json.merge( - supplier_name: supplier.name, - ).merge(supplier_counter_info) - end - - def serialized_with_status_join_requests_and_supplier_counters - as_json.merge(list_active: active? ).merge(join_requests_as_json).merge(supplier_counter_info) - end - def supplier_counter_info { supplier_orders_in_process_count: supplier_orders_in_process_count, @@ -386,21 +303,6 @@ class List Order.active_for_supplier_and_list(supplier_id, id) end - # Return a join requests object in the form of: - # {join_request: [{user_id: '1saf3...', user_email: 'info@qwaiter.com'}, [....]]} - # DEPRICATED IN EMBER - def join_requests_as_json - return @join_requests_as_json if @join_requests_as_json.present? - h = {join_requests: []} - # Handle join requests - if join_request_user_ids.any? - for user in self.class.database.load_document(join_request_user_ids) - h[:join_requests] << {user_id: user.id, user_email: user.email} - end - end - @join_requests_as_json = h - end - def product_categories supplier.product_categories end diff --git a/app/models/list/depricated_serialization.rb b/app/models/list/depricated_serialization.rb new file mode 100644 index 00000000..7cd429dd --- /dev/null +++ b/app/models/list/depricated_serialization.rb @@ -0,0 +1,49 @@ +class List + module DepricatedSerialization + extend ActiveSupport::Concern + + def as_json(*args) + super.merge(id: id, table_number: table_number, has_active_orders: has_active_orders? ) + end + + def with_orders_as_json + return @with_orders_as_json if @with_orders_as_json.present? + orders.include_relations(product_orders: :product) + h = as_json + h[:orders] = [] + h[:list_active] = active? + list_total = 0.0 + for order in orders + ho = {products: []} + ho[:id] = order.id + ho[:state] = order.state + order_total = 0.0 + for product_order in order.product_orders + order_total += (product_order.quantity * product_order.price).round(2) + ho[:products] << {name: product_order.product_name, id: product_order.product_id, number: product_order.quantity, price: product_order.price} + end + ho[:total_amount] = order_total.round(2) + ho[:state] = order.state + list_total += ho[:total_amount] + h[:orders] << ho + end + h[:total_amount] = list_total.round(2) + @with_orders_as_json = h + end + + def with_orders_and_join_requests_as_json + return @with_orders_and_join_requests_as_json if @with_orders_and_join_requests_as_json.present? + @with_orders_and_join_requests_as_json = with_orders_as_json.merge(join_requests_as_json) + end + + def with_orders_and_join_requests_and_supplier_info_as_json + with_orders_and_join_requests_as_json.merge( + supplier_name: supplier.name, + ).merge(supplier_counter_info) + end + + def serialized_with_status_join_requests_and_supplier_counters + as_json.merge(list_active: active? ).merge(join_requests_as_json).merge(supplier_counter_info) + end + end +end diff --git a/app/models/list/join_requests.rb b/app/models/list/join_requests.rb new file mode 100644 index 00000000..6669e6b6 --- /dev/null +++ b/app/models/list/join_requests.rb @@ -0,0 +1,51 @@ +class List + module JoinRequests + extend ActiveSupport::Concern + included do + property :join_request_user_ids, type: Array, default: [] + end + + def join_request_for_user(user) + JoinRequest.new(user: user, list: self) + end + + def join_requests + @join_requests ||= join_request_user_ids.any? ? self.class.database.load_document(join_request_user_ids).map{|user| join_request_for_user(user) } : [] + end + + def send_table_join_request_for_user!(requester) + unless join_request_user_ids.include?(requester.id) or user_ids.include?(requester.id) + @join_requests = nil # bust cache + self.join_request_user_ids << requester.id + self.is_dirty + if save + broadcast_users 'user_join_request', JoinRequestSerializer.new(join_request_for_user(requester)).as_json + end + end + end + + def approve_join_request_for_user!(user) + if join_request_user_ids.include?(user.id) + join_request_user_ids.delete(user.id) + user.active_list_id = self.id + add_user(user) + user.save + self.is_dirty + save and broadcast_users 'join_request_approved', UserUserSerializer.new(user).as_json + end + end + + def reject_join_request_for_user!(user_id) + user_id = user_id.id if user_id.is_a?(User) # allow model to be passed as argument + if join_request_user_ids.include?(user_id) + join_request_user_ids.delete(user_id) + self.is_dirty + if save + # user_id is not part of the list, so should be broadcasted separately + broadcast_user user_id, 'join_request_rejected', id: "jr-#{user_id}" + broadcast_users 'join_request_rejected', id: "jr-#{user_id}" + end + end + end + end +end diff --git a/app/models/supplier.rb b/app/models/supplier.rb index 41b6c652..096098a8 100644 --- a/app/models/supplier.rb +++ b/app/models/supplier.rb @@ -1,6 +1,7 @@ class Supplier include SimplyStored::Couch include ActiveModel::SerializerSupport + include Supplier::Counters view :by_confirmation_token, key: :confirmation_token # devise confirmable devise :database_authenticatable, :recoverable, :rememberable, :trackable, :registerable, :confirmable @@ -96,7 +97,7 @@ class Supplier self.open = true save end - + def mark_as_closed! self.open = false save @@ -128,38 +129,6 @@ class Supplier confirmable end - def self.reset_counters! - # Set all known counters to zero - Qwaiter::Couchbase.design_doc('supplier').counters(reduce: false).each{|counter| Qwaiter::Counter.set counter.key, 0} - - spec = Order.by_supplier_id_and_state(reduce: true, group_level: 2) - reset_order_counters_with_spec spec - end - - def reset_counters! - spec = Order.by_supplier_id_and_state(startkey: [id], endkey: [id, {}], reduce: true, group_level: 2) - self.class.reset_order_counters_with_spec spec - end - - def self.reset_order_counters_with_spec(spec) - # taken from the couch_potato source since we want a model/custom mix here (hmmmm, something that should be fixed in the couchbase version) - results = CouchPotato::View::ViewQuery.new( - database.couchrest_database, - spec.design_document, - {spec.view_name => { map: spec.map_function, reduce: spec.reduce_function} }, - ({spec.list_name => spec.list_function} unless spec.list_name.nil?), - spec.language - ).query_view!(spec.view_parameters) - Array.wrap(results['rows']).each do |result| - supplier_id, state = result['key'] - case state - when 'placed' then Qwaiter::Counter.set "supplier_counter:#{supplier_id}:orders_placed", result['value'] - when 'active' then Qwaiter::Counter.set "supplier_counter:#{supplier_id}:orders_in_process", result['value'] - end - end - end - - # Send confirmation instructions by email def send_confirmation_instructions(*args) self.confirmation_token = nil if reconfirmation_required? @@ -183,43 +152,6 @@ class Supplier true end - # COUNTER SECTION. Can be a concern in a future - def increment_orders_in_process_count! - Qwaiter::Counter.incr self.class.orders_in_process_counter_key(id) - end - - def increment_orders_placed_count! - Qwaiter::Counter.incr self.class.orders_placed_counter_key(id) - end - - def decrement_orders_in_process_count! - Qwaiter::Counter.decr self.class.orders_in_process_counter_key(id) - end - - def decrement_orders_placed_count! - Qwaiter::Counter.decr self.class.orders_placed_counter_key(id) - end - - def orders_in_process_count - Qwaiter::Counter.get self.class.orders_in_process_counter_key(id) - end - - def orders_placed_count - Qwaiter::Counter.get self.class.orders_placed_counter_key(id) - end - - def self.orders_in_process_counter_key(id) - "supplier_counter:#{id}:orders_in_process" - end - - def self.orders_placed_counter_key(id) - "supplier_counter:#{id}:orders_placed" - end - - def active_order_count - orders_in_process_count + orders_placed_count - end - private def add_section_on_create diff --git a/app/models/supplier/counters.rb b/app/models/supplier/counters.rb new file mode 100644 index 00000000..0b8a9079 --- /dev/null +++ b/app/models/supplier/counters.rb @@ -0,0 +1,84 @@ +class Supplier + module Counters + extend ActiveSupport::Concern + + def reset_counters! + spec = Order.by_supplier_id_and_state(startkey: [id], endkey: [id, {}], reduce: true, group_level: 2) + self.class.reset_order_counters_with_spec spec + end + + def increment_orders_in_process_count! + Qwaiter::Counter.incr orders_in_process_counter_key + end + + def increment_orders_placed_count! + Qwaiter::Counter.incr orders_placed_counter_key + end + + def decrement_orders_in_process_count! + Qwaiter::Counter.decr orders_in_process_counter_key + end + + def decrement_orders_placed_count! + Qwaiter::Counter.decr orders_placed_counter_key + end + + def orders_in_process_count + Qwaiter::Counter.get orders_in_process_counter_key + end + + def orders_placed_count + Qwaiter::Counter.get orders_placed_counter_key + end + + + def active_order_count + orders_in_process_count + orders_placed_count + end + + def orders_in_process_counter_key + self.class.orders_in_process_counter_key(id) + end + + def orders_placed_counter_key + self.class.orders_placed_counter_key(id) + end + + module ClassMethods + + def orders_in_process_counter_key(id) + "supplier_counter:#{id}:orders_in_process" + end + + def orders_placed_counter_key(id) + "supplier_counter:#{id}:orders_placed" + end + + def reset_counters! + # Set all known counters to zero + Qwaiter::Couchbase.flush_counters! + + spec = Order.by_supplier_id_and_state(reduce: true, group_level: 2) + reset_order_counters_with_spec spec + end + + def reset_order_counters_with_spec(spec) + # taken from the couch_potato source since we want a model/custom mix here (hmmmm, something that should be fixed in the couchbase version) + results = CouchPotato::View::ViewQuery.new( + database.couchrest_database, + spec.design_document, + {spec.view_name => { map: spec.map_function, reduce: spec.reduce_function} }, + ({spec.list_name => spec.list_function} unless spec.list_name.nil?), + spec.language + ).query_view!(spec.view_parameters) + Array.wrap(results['rows']).each do |result| + supplier_id, state = result['key'] + case state + when 'placed' then Qwaiter::Counter.set orders_placed_counter_key(supplier_id), result['value'] + when 'active' then Qwaiter::Counter.set orders_in_process_counter_key(supplier_id), result['value'] + end + end + end + end + end +end diff --git a/bin/drb_counter.rb b/bin/drb_counter.rb new file mode 100755 index 00000000..d3e1d362 --- /dev/null +++ b/bin/drb_counter.rb @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# Make drb server +require 'rubygems' +require File.expand_path('../lib/in_memory_q_counter', File.dirname(__FILE__)) +require 'drb' +require 'daemons' +drb_port = 9022 +puts "Counter server running at port #{drb_port}" +Daemons.run_proc('DRBcounter', dir_mode: :normal, dir: File.expand_path("#{File.dirname(__FILE__)}/../tmp/pids")) do + DRb.start_service "druby://:#{drb_port}", InMemoryQCounter.new + # trap("INT") { DRb.stop_service } + DRb.thread.join +end diff --git a/config/initializers/qwaiter_settings.rb b/config/initializers/qwaiter_settings.rb index ea56e7c2..f8734c4c 100644 --- a/config/initializers/qwaiter_settings.rb +++ b/config/initializers/qwaiter_settings.rb @@ -1,2 +1,10 @@ Qwaiter.event_host = "http://#{Rails.env.production? ? 'events.qwaiter.com' : 'localhost'}:9296/faye" Qwaiter.broadcaster = Qwaiter::Broadcaster::Faye.new + +# use the connection from couchbase-structures/documents +# will be overwritten in the specs since flushing the real +# thing is difficult +# Qwaiter::Counter.connection = $cb unless Rails.env.test? + +# Use the Drb counter +Qwaiter::Counter.connection = Qwaiter::DrbCounter.object unless Rails.env.test? diff --git a/spec/support/in_memory_q_counter.rb b/lib/in_memory_q_counter.rb similarity index 80% rename from spec/support/in_memory_q_counter.rb rename to lib/in_memory_q_counter.rb index ff6b9597..d4088e2d 100644 --- a/spec/support/in_memory_q_counter.rb +++ b/lib/in_memory_q_counter.rb @@ -41,11 +41,3 @@ class InMemoryQCounter store.clear end end - -=begin Make drb server -require 'drb' -DRb.start_service 'druby://:9000', InMemoryQCounter.new -puts "Counter server running at #{DRb.uri}" -trap("INT") { DRb.stop_service } -DRb.thread.join -=end diff --git a/lib/qwaiter.rb b/lib/qwaiter.rb index 2baa638e..a03faa86 100644 --- a/lib/qwaiter.rb +++ b/lib/qwaiter.rb @@ -7,6 +7,7 @@ module Qwaiter autoload :Counter autoload :Broadcaster autoload :Couchbase + autoload :DrbCounter def self.broadcast_user(uid, event, data) message = {channel: "/user/#{uid}", data: {event: event, data: data}} @@ -18,4 +19,3 @@ module Qwaiter broadcaster.broadcast message end end - diff --git a/lib/qwaiter/couchbase.rb b/lib/qwaiter/couchbase.rb index 1969d4e6..ff3c2cac 100644 --- a/lib/qwaiter/couchbase.rb +++ b/lib/qwaiter/couchbase.rb @@ -5,13 +5,20 @@ module Qwaiter end def self.load_design_docs! + return unless connection.present? Dir.glob(Rails.root.join('config/couchbase/design_docs', "*.json")).each do |design_doc| connection.save_design_doc File.open(design_doc) end end def self.design_doc(name) + return unless connection.present? connection.design_docs[name] end + + def self.flush_counters! + return unless connection.present? + design_doc('supplier').counters(reduce: false).each{|counter| Qwaiter::Counter.set counter.key, 0} + end end end diff --git a/lib/qwaiter/counter.rb b/lib/qwaiter/counter.rb index c260b107..03388971 100644 --- a/lib/qwaiter/counter.rb +++ b/lib/qwaiter/counter.rb @@ -22,8 +22,3 @@ module Qwaiter 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 unless Rails.env.test? diff --git a/lib/qwaiter/drb_counter.rb b/lib/qwaiter/drb_counter.rb new file mode 100644 index 00000000..6b9da2ae --- /dev/null +++ b/lib/qwaiter/drb_counter.rb @@ -0,0 +1,8 @@ +module Qwaiter + module DrbCounter + def self.object + require 'drb' + DRbObject.new_with_uri('druby://localhost:9022') + end + end +end diff --git a/spec/models/list/join_requests_spec.rb b/spec/models/list/join_requests_spec.rb new file mode 100644 index 00000000..ab12f8dd --- /dev/null +++ b/spec/models/list/join_requests_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + + +describe List do + let(:supplier) { create :supplier } + let(:supplier_password){'secret1'} + let(:supplier) { create :supplier, email: 'supplier@qwaiter.com', password: supplier_password, confirmation_token: 'abc', confirmed_at: Time.now.utc, open: true } + let(:user) { create :user } + let(:section) { create :section, supplier: supplier} + let(:table) { create :table, supplier: supplier} + let(:list_options){ {supplier: supplier, table: table, section: section, user_ids: [user.id]} } + let(:list){ create :list, list_options} + + describe 'join requests' do + describe '#send_table_join_request_for_user' do + it "does not add an existing user to join_request_user_ids" do + list.send_table_join_request_for_user! user + expect(list.join_request_user_ids).not_to include user.id + end + it "adds a user to join_request_user_ids" do + other_user = create :user + list.send_table_join_request_for_user! other_user + # test through persistance + list.reload + expect(list.join_request_user_ids).to eq [other_user.id] + end + + it "does not add a user multiple times" do + other_user = create :user + 2.times { list.send_table_join_request_for_user! other_user } + expect(list.join_request_user_ids).to eq [other_user.id] + end + + it "broadcasts it to the user" do + other_user = create :user + expect{ + list.send_table_join_request_for_user! other_user + }.to broadcast_to_user(user.id).message('user_join_request').with( + hash_including(:users, :join_request) + ) + end + end + + describe "#approve_join_request_for_user" do + let(:joining_user){ create :user } + + before do + list_options[:join_request_user_ids] = [joining_user.id] + end + + it "does not raise error when no user is associated" do + unlinked_user = create :user + expect{ list.approve_join_request_for_user!(unlinked_user) }.not_to raise_error + end + + it "removes the user as wanting to join" do + list.approve_join_request_for_user! joining_user + list.join_request_user_ids.should_not include joining_user.id + + # persistance check + list.reload + list.join_request_user_ids.should_not include joining_user.id + end + + it "sets the active list of the user to the specific list" do + list.approve_join_request_for_user! joining_user + joining_user.active_list_id.should == list.id + + # persistance check + joining_user.reload + joining_user.active_list_id.should == list.id + end + + it "broadcasts the event to the user itself" do + joining_user + expect{ list.approve_join_request_for_user! joining_user } + .to broadcast_to_user(joining_user).message('join_request_approved') + .with( hash_including(:user) ) + end + + it "broadcasts the event to other associated users" do + expect{ list.approve_join_request_for_user! joining_user } + .to broadcast_to_user(user).message('join_request_approved') + .with( hash_including(:user) ) + end + end + + describe "#reject_join_request_for_user" do + let(:joining_user){ create :user } + + before do + list_options[:join_request_user_ids] = [joining_user.id] + end + + it "does not raise error when no user is associated" do + unlinked_user = create :user + expect{ list.reject_join_request_for_user!(unlinked_user) }.not_to raise_error + end + + it "removes the user as wanting to join" do + list.reject_join_request_for_user! joining_user + list.join_request_user_ids.should_not include joining_user.id + + # persistance check + list.reload + list.join_request_user_ids.should_not include joining_user.id + end + + it "does not set the active list of the user to the specific list" do + list.reject_join_request_for_user! joining_user + joining_user.active_list_id.should_not be_present + end + + it "broadcasts the event to the user itself" do + joining_user + expect{ list.reject_join_request_for_user! joining_user } + .to broadcast_to_user(joining_user).message('join_request_rejected') + .with( id: "jr-#{joining_user.id}" ) + end + + it "broadcasts the event to other associated users" do + expect{ list.reject_join_request_for_user! joining_user } + .to broadcast_to_user(user).message('join_request_rejected') + .with( id: "jr-#{joining_user.id}" ) + end + end + + end +end diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index decb5bd0..98e2d4ce 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -8,19 +8,12 @@ describe List do let(:user) { create :user } let(:section) { create :section, supplier: supplier} let(:table) { create :table, supplier: supplier} - let(:list){ create :list, supplier: supplier, table: table, section: section, user_ids: [user.id] } + let(:list_options){ {supplier: supplier, table: table, section: section, user_ids: [user.id]} } + let(:list){ create :list, list_options} let(:product){ create :product, price: 2.22, supplier: supplier } let(:order) { create :order, user: user, list: list, supplier: supplier, section: section } let(:product_order ){ create :product_order, order: order, product: product, quantity: 3, price: 2.11 } subject { list } - describe :as_json do - it 'should include _id in as_json serialization' do - list.as_json.keys.map(&:to_sym).should include :_id - end - it 'should include table_number in as_json serialization' do - list.as_json.keys.should include :table_number - end - end describe :mark_as_paid do it "should set paid_at to a time" do @@ -163,35 +156,4 @@ describe List do describe 'product order creation' end - - describe 'join requests' do - describe '#send_table_join_request_for_user' do - it "does not add an existing user to join_request_user_ids" do - list.send_table_join_request_for_user! user - expect(list.join_request_user_ids).not_to include user.id - end - it "adds a user to join_request_user_ids" do - other_user = create :user - list.send_table_join_request_for_user! other_user - # test through persistance - list.reload - expect(list.join_request_user_ids).to eq [other_user.id] - end - - it "does not add a user multiple times" do - other_user = create :user - 2.times { list.send_table_join_request_for_user! other_user } - expect(list.join_request_user_ids).to eq [other_user.id] - end - - it "broadcasts it to the user" do - other_user = create :user - expect{ - list.send_table_join_request_for_user! other_user - }.to broadcast_to_user(user.id).message('user_join_request').with( - hash_including(:users, :join_request) - ) - end - end - end end diff --git a/spec/models/supplier_spec.rb b/spec/models/supplier_spec.rb index 9c896b0c..b285bc9a 100644 --- a/spec/models/supplier_spec.rb +++ b/spec/models/supplier_spec.rb @@ -16,18 +16,18 @@ describe Supplier do supplier2.orders_placed_count.should == 3 end - it 'cleans counter values if orders are no longer available' do - old_connection = Qwaiter::Counter.connection - # this spec should run on the couchbase database - Qwaiter::Counter.connection = $cb - supplier = create :supplier - Qwaiter::Counter.set "supplier_counter:#{supplier.id}:orders_placed", 9 - supplier.orders_placed_count.should == 9 - Supplier.reset_counters! - sleep 1 - supplier.orders_placed_count.should == 0 - Qwaiter::Counter.connection = old_connection - end + # it 'cleans counter values if orders are no longer available', broken: defined?($cb) do + # old_connection = Qwaiter::Counter.connection + # # this spec should run on the couchbase database + # Qwaiter::Counter.connection = $cb + # supplier = create :supplier + # Qwaiter::Counter.set "supplier_counter:#{supplier.id}:orders_placed", 9 + # supplier.orders_placed_count.should == 9 + # Supplier.reset_counters! + # sleep 1 + # supplier.orders_placed_count.should == 0 + # Qwaiter::Counter.connection = old_connection + # end end describe '#reset_counters!' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9642a177..2147663e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,11 +2,12 @@ ENV["RAILS_ENV"] ||= 'test' require 'simplecov' SimpleCov.start 'rails' -require File.expand_path("../../config/environment", __FILE__) +require File.expand_path("../config/environment", File.dirname(__FILE__)) require 'rspec/rails' require 'rspec/matchers' require 'capybara/rspec' require 'turnip/capybara' +require 'in_memory_q_counter' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. @@ -44,10 +45,20 @@ module SpecSelectorHelpers end end -class Couchbase::View - alias :old_initialize :initialize - def initialize(bucket, endpoint, params = {}) - old_initialize(bucket, endpoint, params.merge(stale: false)) +class TestCounter < InMemoryQCounter + def incr(*args) + result = super + puts "Counter incr called with #{args.inspect} giving result #{result}" + result + end +end + +if defined?(Couchbase) + class Couchbase::View + alias :old_initialize :initialize + def initialize(bucket, endpoint, params = {}) + old_initialize(bucket, endpoint, params.merge(stale: false)) + end end end @@ -108,11 +119,7 @@ RSpec.configure do |config| config.before :suite do Qwaiter::Couchbase.load_design_docs! # NOT THREADSAFE!!!!!! but good enough for testing since the real couchbase flush is slowwwwww.... - Qwaiter::Counter.connection = InMemoryQCounter.new - # Threadsafe would be using the drb counter - # require 'drb' - # counter = DRbObject.new nil, 'druby://:9000' - # Qwaiter::Counter.connection = counter + Qwaiter::Counter.connection = TestCounter.new end config.before :each do diff --git a/spec/support/matchers/broadcast_to_user_matcher.rb b/spec/support/matchers/broadcast_to_user_matcher.rb index f015e1e5..17f1142e 100644 --- a/spec/support/matchers/broadcast_to_user_matcher.rb +++ b/spec/support/matchers/broadcast_to_user_matcher.rb @@ -14,6 +14,7 @@ module Matchers def initialize(user_id) @user_id = user_id + @user_id = @user_id.id if @user_id.is_a?(User) end def matches?(block) @@ -24,8 +25,9 @@ module Matchers Qwaiter.broadcaster = old_broadcaster relevant_broadcasts = test_broadcaster.broadcasts.select{|b| b[:channel] =~ /^\/user\/#{@user_id}/ && b[:data][:event] == @message} + @failure_message = "User #{@user_id} did not receive any broadcasts" and return false if relevant_broadcasts.empty? @failure_debug_content = "was #{relevant_broadcasts.map{|b| b[:data][:data].inspect}.join(" and ")}" - relevant_broadcasts.any?{|b| @target_object === b[:data][:data]} + relevant_broadcasts.any? { |b| @target_object === b[:data][:data] } end def message(msg) @@ -39,7 +41,7 @@ module Matchers end def failure_message - "user #{@user_id} did not receive broadcast #{@message} with #{@target_object.inspect} #{@failure_debug_content}" + @failure_message || "user #{@user_id} did not receive broadcast #{@message} with #{@target_object.inspect} #{@failure_debug_content}" end def supports_block_expectations?; true; end end