Commit 26fc3a60 by Brian Beggs

Merge pull request #132 from edx/bbeggs/mongodb-3.0-PLAT-730

Update Mongoid to 5.x release (mongodb 3.x + compatibility)
parents 302eaf6b b19efebc
...@@ -22,3 +22,4 @@ Alan Boudreault <alan@alanb.ca> ...@@ -22,3 +22,4 @@ Alan Boudreault <alan@alanb.ca>
Matjaz Gregoric <mtyaka@gmail.com> Matjaz Gregoric <mtyaka@gmail.com>
Ben McMorran <ben.mcmorran@gmail.com> Ben McMorran <ben.mcmorran@gmail.com>
Bill DeRusha <bill@edx.org> Bill DeRusha <bill@edx.org>
Brian Beggs <macdiesel@gmail.com>
...@@ -14,25 +14,21 @@ gem 'sinatra' ...@@ -14,25 +14,21 @@ gem 'sinatra'
gem 'yajl-ruby' gem 'yajl-ruby'
gem 'ampex' gem 'mongoid', "~>5.0"
gem 'bson', '~>3.1'
gem 'mongo'
gem 'moped', "1.5.1"
gem 'mongoid', "3.0.15"
gem 'bson_ext' gem 'bson_ext'
gem 'protected_attributes'
gem 'delayed_job' gem 'delayed_job'
gem 'delayed_job_mongoid', :git => 'https://github.com/dementrock/delayed_job_mongoid.git' gem 'delayed_job_mongoid'
gem "enumerize", "~>0.8.0"
gem 'mongoid-tree', :git => 'https://github.com/dementrock/mongoid-tree.git'
gem 'voteable_mongo', :git => 'https://github.com/dementrock/voteable_mongo.git'
gem 'mongoid_magic_counter_cache', :git => 'https://github.com/dementrock/mongoid-magic-counter-cache.git'
gem 'kaminari', :require => 'kaminari/sinatra', :git => 'https://github.com/dementrock/kaminari.git' gem "enumerize"
gem 'mongoid-tree', :git => 'https://github.com/macdiesel/mongoid-tree'
gem 'rs_voteable_mongo', :git => 'https://github.com/navneet35371/voteable_mongo.git'
gem 'mongoid_magic_counter_cache'
gem 'faker' gem 'faker'
gem 'will_paginate_mongoid' gem 'will_paginate_mongoid', "~>2.0"
gem 'rdiscount' gem 'rdiscount'
gem 'nokogiri' gem 'nokogiri'
...@@ -49,12 +45,14 @@ group :test do ...@@ -49,12 +45,14 @@ group :test do
gem 'guard' gem 'guard'
gem 'guard-unicorn' gem 'guard-unicorn'
gem 'simplecov', :require => false gem 'simplecov', :require => false
gem 'database_cleaner' # database_cleaner 1.5.1 which is compatible with Mongoid 5 has not been released
# to rubygems yet, so pull it from github.
gem 'database_cleaner', :git => 'https://github.com/DatabaseCleaner/database_cleaner', :ref => 'b87f00320f8aa0f7e499d183128f05ce29cedc33'
end end
gem 'newrelic_rpm' gem 'newrelic_rpm'
gem 'newrelic_moped'
gem 'unicorn' gem 'unicorn'
gem "rack-timeout", "0.1.0beta3" gem "rack-timeout"
gem "i18n" gem "i18n"
gem "rack-contrib", :git => 'https://github.com/rack/rack-contrib.git', :ref => '6ff3ca2b2d988911ca52a2712f6a7da5e064aa27' gem "rack-contrib", :git => 'https://github.com/rack/rack-contrib.git', :ref => '6ff3ca2b2d988911ca52a2712f6a7da5e064aa27'
GIT GIT
remote: https://github.com/dementrock/delayed_job_mongoid.git remote: https://github.com/DatabaseCleaner/database_cleaner
revision: 48b1420d59bc01e0b1aba1c2ad66bda4a5e04b9a revision: b87f00320f8aa0f7e499d183128f05ce29cedc33
ref: b87f00320f8aa0f7e499d183128f05ce29cedc33
specs: specs:
delayed_job_mongoid (1.0.8) database_cleaner (1.5.1)
delayed_job (~> 3.0.0)
mongoid (>= 3.0.0.rc)
GIT GIT
remote: https://github.com/dementrock/kaminari.git remote: https://github.com/macdiesel/mongoid-tree
revision: 82a38e07db1ca1598c8daf073a8f6be22ae714d6 revision: b381dd56f1b3b061df8f4b4181d5440dea1602d1
specs: specs:
kaminari (0.13.0) mongoid-tree (2.0.0)
actionpack (>= 3.0.0) mongoid (>= 4.0, <= 5.0)
activesupport (>= 3.0.0)
GIT GIT
remote: https://github.com/dementrock/mongoid-magic-counter-cache.git remote: https://github.com/navneet35371/voteable_mongo.git
revision: 28bc5e617cab19187b323e7d97d49fe73a7de68a revision: 55fcfe76705ab5da1c9e5670594331b33954c545
specs: specs:
mongoid_magic_counter_cache (0.1.1) rs_voteable_mongo (1.0.2)
mongoid (>= 3.0) mongoid (>= 3.0, <= 5.0)
rake
GIT
remote: https://github.com/dementrock/mongoid-tree.git
revision: 5aa7a4ee16cd90dbbcac3ad702446d2119e971df
specs:
mongoid-tree (1.0.0)
mongoid (>= 3.0, <= 4.0)
GIT
remote: https://github.com/dementrock/voteable_mongo.git
revision: 538e86856daa1c180ba80b7c6f2805e531ba420c
specs:
voteable_mongo (0.9.3)
GIT GIT
remote: https://github.com/rack/rack-contrib.git remote: https://github.com/rack/rack-contrib.git
...@@ -46,39 +30,32 @@ GIT ...@@ -46,39 +30,32 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionpack (3.2.8) activemodel (4.2.4)
activemodel (= 3.2.8) activesupport (= 4.2.4)
activesupport (= 3.2.8) builder (~> 3.1)
builder (~> 3.0.0) activesupport (4.2.4)
erubis (~> 2.7.0) i18n (~> 0.7)
journey (~> 1.0.4) json (~> 1.7, >= 1.7.7)
rack (~> 1.4.0) minitest (~> 5.1)
rack-cache (~> 1.2) thread_safe (~> 0.3, >= 0.3.4)
rack-test (~> 0.6.1) tzinfo (~> 1.1)
sprockets (~> 2.1.3) ansi (1.5.0)
activemodel (3.2.8) bson (3.2.4)
activesupport (= 3.2.8) bson_ext (1.5.1)
builder (~> 3.0.0) builder (3.2.2)
activesupport (3.2.8)
i18n (~> 0.6)
multi_json (~> 1.0)
ampex (2.0.0)
blankslate
ansi (1.4.3)
blankslate (2.1.2.4)
bson (1.6.4)
bson_ext (1.6.4)
bson (~> 1.6.4)
builder (3.0.4)
coderay (1.0.7) coderay (1.0.7)
dalli (2.1.0) dalli (2.1.0)
database_cleaner (1.2.0) delayed_job (4.1.1)
delayed_job (3.0.3) activesupport (>= 3.0, < 5.0)
activesupport (~> 3.0) delayed_job_mongoid (2.2.0)
delayed_job (>= 3.0, < 5)
mongoid (>= 3.0, < 6)
mongoid-compatibility
diff-lcs (1.1.3) diff-lcs (1.1.3)
enumerize (0.8.0) domain_name (0.5.24)
unf (>= 0.0.5, < 1.0.0)
enumerize (0.11.0)
activesupport (>= 3.2) activesupport (>= 3.2)
erubis (2.7.0)
faker (1.0.1) faker (1.0.1)
i18n (~> 0.4) i18n (~> 0.4)
guard (1.3.2) guard (1.3.2)
...@@ -87,47 +64,54 @@ GEM ...@@ -87,47 +64,54 @@ GEM
guard-unicorn (0.0.7) guard-unicorn (0.0.7)
guard (>= 1.1) guard (>= 1.1)
hashr (0.0.22) hashr (0.0.22)
hike (1.2.1) http-cookie (1.0.2)
i18n (0.6.9) domain_name (~> 0.5)
journey (1.0.4) i18n (0.7.0)
kgio (2.7.4) json (1.8.3)
kgio (2.10.0)
listen (0.5.0) listen (0.5.0)
method_source (0.8) method_source (0.8)
mime-types (2.2) mime-types (2.6.1)
mongo (1.6.4) minitest (5.8.1)
bson (~> 1.6.4) mongo (2.1.1)
mongoid (3.0.15) bson (~> 3.0)
activemodel (~> 3.1) mongoid (5.0.0)
moped (~> 1.1) activemodel (~> 4.0)
origin (~> 1.0) mongo (~> 2.1)
tzinfo (~> 0.3.22) origin (~> 2.1)
moped (1.5.1) tzinfo (>= 0.3.37)
multi_json (1.10.0) mongoid-compatibility (0.3.1)
newrelic_moped (1.0.0) activesupport
moped mongoid (>= 2.0)
newrelic_rpm (>= 3.7) mongoid_magic_counter_cache (1.1.1)
newrelic_rpm (3.11.2.286) mongoid
rake
multi_json (1.11.2)
netrc (0.10.3)
newrelic_rpm (3.13.2.302)
nokogiri (1.5.5) nokogiri (1.5.5)
origin (1.1.0) origin (2.1.1)
protected_attributes (1.1.3)
activemodel (>= 4.0.1, < 5.0)
pry (0.9.10) pry (0.9.10)
coderay (~> 1.0.5) coderay (~> 1.0.5)
method_source (~> 0.8) method_source (~> 0.8)
slop (~> 3.3.1) slop (~> 3.3.1)
pry-nav (0.2.2) pry-nav (0.2.2)
pry (~> 0.9.10) pry (~> 0.9.10)
rack (1.4.1) rack (1.6.4)
rack-cache (1.2)
rack (>= 0.4)
rack-protection (1.2.0) rack-protection (1.2.0)
rack rack
rack-test (0.6.1) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rack-timeout (0.1.0beta3) rack-timeout (0.3.2)
raindrops (0.10.0) raindrops (0.15.0)
rake (10.3.1) rake (10.4.2)
rdiscount (1.6.8) rdiscount (1.6.8)
rest-client (1.6.7) rest-client (1.8.0)
mime-types (>= 1.16) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rspec (2.11.0) rspec (2.11.0)
rspec-core (~> 2.11.0) rspec-core (~> 2.11.0)
rspec-expectations (~> 2.11.0) rspec-expectations (~> 2.11.0)
...@@ -145,11 +129,8 @@ GEM ...@@ -145,11 +129,8 @@ GEM
rack-protection (~> 1.2) rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3) tilt (~> 1.3, >= 1.3.3)
slop (3.3.2) slop (3.3.2)
sprockets (2.1.3)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
thor (0.16.0) thor (0.16.0)
thread_safe (0.3.5)
tilt (1.3.3) tilt (1.3.3)
tire (0.6.2) tire (0.6.2)
activemodel (>= 3.0) activemodel (>= 3.0)
...@@ -161,56 +142,57 @@ GEM ...@@ -161,56 +142,57 @@ GEM
rest-client (~> 1.6) rest-client (~> 1.6)
tire-contrib (0.1.1) tire-contrib (0.1.1)
tire tire
tzinfo (0.3.38) tzinfo (1.2.2)
unicorn (4.3.1) thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
raindrops (~> 0.7) raindrops (~> 0.7)
will_paginate (3.0.4) will_paginate (3.0.7)
will_paginate_mongoid (1.1.0) will_paginate_mongoid (2.0.1)
mongoid (>= 2.4) mongoid
will_paginate (~> 3.0) will_paginate (~> 3.0)
yajl-ruby (1.1.0) yajl-ruby (1.2.1)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
ampex bson (~> 3.1)
bson_ext bson_ext
bundler bundler
dalli dalli
database_cleaner database_cleaner!
delayed_job delayed_job
delayed_job_mongoid! delayed_job_mongoid
enumerize (~> 0.8.0) enumerize
faker faker
guard guard
guard-unicorn guard-unicorn
i18n i18n
kaminari! mongoid (~> 5.0)
mongo
mongoid (= 3.0.15)
mongoid-tree! mongoid-tree!
mongoid_magic_counter_cache! mongoid_magic_counter_cache
moped (= 1.5.1)
newrelic_moped
newrelic_rpm newrelic_rpm
nokogiri nokogiri
protected_attributes
pry pry
pry-nav pry-nav
rack-contrib! rack-contrib!
rack-test rack-test
rack-timeout (= 0.1.0beta3) rack-timeout
rake rake
rdiscount rdiscount
rest-client rest-client
rs_voteable_mongo!
rspec rspec
simplecov simplecov
sinatra sinatra
tire (= 0.6.2) tire (= 0.6.2)
tire-contrib tire-contrib
unicorn unicorn
voteable_mongo! will_paginate_mongoid (~> 2.0)
will_paginate_mongoid
yajl-ruby yajl-ruby
...@@ -159,7 +159,7 @@ namespace :db do ...@@ -159,7 +159,7 @@ namespace :db do
task :seed_fast => :environment do task :seed_fast => :environment do
ADDITIONAL_COMMENTS_PER_THREAD = 20 ADDITIONAL_COMMENTS_PER_THREAD = 20
config = YAML.load_file("config/mongoid.yml")[Sinatra::Base.environment]["sessions"]["default"] config = YAML.load_file("config/mongoid.yml")[Sinatra::Base.environment]["clients"]["default"]
connnection = Mongo::Connection.new(config["hosts"][0].split(":")[0], config["hosts"][0].split(":")[1]) connnection = Mongo::Connection.new(config["hosts"][0].split(":")[0], config["hosts"][0].split(":")[1])
db = Mongo::Connection.new.db(config["database"]) db = Mongo::Connection.new.db(config["database"])
coll = db.collection("contents") coll = db.collection("contents")
...@@ -248,7 +248,6 @@ namespace :search do ...@@ -248,7 +248,6 @@ namespace :search do
end end
def import_from_cursor(cursor, index, opts) def import_from_cursor(cursor, index, opts)
Mongoid.identity_map_enabled = true
tot = cursor.count tot = cursor.count
cnt = 0 cnt = 0
t = Time.now t = Time.now
...@@ -259,7 +258,6 @@ namespace :search do ...@@ -259,7 +258,6 @@ namespace :search do
LOG.info "#{index.name}: imported #{cnt} of #{tot} (#{pct_complete}% complete after #{elapsed_secs} seconds)" LOG.info "#{index.name}: imported #{cnt} of #{tot} (#{pct_complete}% complete after #{elapsed_secs} seconds)"
end end
cnt += documents.length cnt += documents.length
Mongoid::IdentityMap.clear
sleep opts[:sleep_time] sleep opts[:sleep_time]
documents documents
end end
......
...@@ -8,7 +8,11 @@ put "#{APIPREFIX}/comments/:comment_id" do |comment_id| ...@@ -8,7 +8,11 @@ put "#{APIPREFIX}/comments/:comment_id" do |comment_id|
if params.has_key?("endorsed") if params.has_key?("endorsed")
new_endorsed_val = Boolean.mongoize(params["endorsed"]) new_endorsed_val = Boolean.mongoize(params["endorsed"])
if new_endorsed_val != comment.endorsed if new_endorsed_val != comment.endorsed
if params["endorsement_user_id"].nil?
endorsement = nil
else
endorsement = {:user_id => params["endorsement_user_id"], :time => DateTime.now} endorsement = {:user_id => params["endorsement_user_id"], :time => DateTime.now}
end
updated_content["endorsement"] = new_endorsed_val ? endorsement : nil updated_content["endorsement"] = new_endorsed_val ? endorsement : nil
end end
end end
......
require 'new_relic/agent/method_tracer'
get "#{APIPREFIX}/search/threads" do get "#{APIPREFIX}/search/threads" do
local_params = params # Necessary for params to be available inside blocks local_params = params # Necessary for params to be available inside blocks
group_ids = get_group_ids_from_params(local_params) group_ids = get_group_ids_from_params(local_params)
......
require 'new_relic/agent/method_tracer'
post "#{APIPREFIX}/users" do post "#{APIPREFIX}/users" do
user = User.new(external_id: params["id"]) user = User.new(external_id: params["id"])
user.username = params["username"] user.username = params["username"]
......
...@@ -19,29 +19,6 @@ module CommentService ...@@ -19,29 +19,6 @@ module CommentService
API_PREFIX = "/api/#{API_VERSION}" API_PREFIX = "/api/#{API_VERSION}"
end end
if ["staging", "production", "loadtest", "edgestage","edgeprod"].include? environment
require 'newrelic_rpm'
require 'new_relic/agent/method_tracer'
Moped::Session.class_eval do
include NewRelic::Agent::MethodTracer
add_method_tracer :new
add_method_tracer :use
add_method_tracer :login
end
Moped::Cluster.class_eval do
include NewRelic::Agent::MethodTracer
add_method_tracer :with_primary
add_method_tracer :nodes
end
Moped::Node.class_eval do
include NewRelic::Agent::MethodTracer
add_method_tracer :command
add_method_tracer :connect
add_method_tracer :flush
add_method_tracer :refresh
end
end
if ENV["ENABLE_GC_PROFILER"] if ENV["ENABLE_GC_PROFILER"]
GC::Profiler.enable GC::Profiler.enable
end end
...@@ -56,11 +33,12 @@ end ...@@ -56,11 +33,12 @@ end
Mongoid.load!("config/mongoid.yml", environment) Mongoid.load!("config/mongoid.yml", environment)
Mongoid.logger.level = Logger::INFO Mongoid.logger.level = Logger::INFO
Moped.logger.level = ENV["ENABLE_MOPED_DEBUGGING"] ? Logger::DEBUG : Logger::INFO Mongo::Logger.logger.level = ENV["ENABLE_MONGO_DEBUGGING"] ? Logger::DEBUG : Logger::INFO
# set up i18n # set up i18n
I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml').to_s] I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml').to_s]
I18n.default_locale = CommentService.config[:default_locale] I18n.default_locale = CommentService.config[:default_locale]
I18n.enforce_available_locales = false
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
use Rack::Locale use Rack::Locale
...@@ -97,27 +75,6 @@ before do ...@@ -97,27 +75,6 @@ before do
content_type "application/json" content_type "application/json"
end end
if ENV["ENABLE_IDMAP_LOGGING"]
after do
idmap = Mongoid::Threaded.identity_map
vals = {
"pid" => Process.pid,
"dyno" => ENV["DYNO"],
"request_id" => params[:request_id]
}
idmap.each {|k, v| vals["idmap_count_#{k.to_s}"] = v.size }
logger.info vals.map{|e| e.join("=") }.join(" ")
end
end
# Enable the identity map. The middleware ensures that the identity map is
# cleared for every request.
Mongoid.identity_map_enabled = true
use Rack::Mongoid::Middleware::IdentityMap
# use yajl implementation for to_json. # use yajl implementation for to_json.
# https://github.com/brianmario/yajl-ruby#json-gem-compatibility-api # https://github.com/brianmario/yajl-ruby#json-gem-compatibility-api
# #
...@@ -128,17 +85,28 @@ require 'yajl/json_gem' ...@@ -128,17 +85,28 @@ require 'yajl/json_gem'
# patch json serialization of ObjectIds to work properly with yajl. # patch json serialization of ObjectIds to work properly with yajl.
# See https://groups.google.com/forum/#!topic/mongoid/MaXFVw7D_4s # See https://groups.google.com/forum/#!topic/mongoid/MaXFVw7D_4s
module Moped # Note that BSON was moved from Moped::BSON::ObjectId to BSON::ObjectId
module BSON module BSON
class ObjectId class ObjectId
def to_json def to_json
self.to_s.to_json self.to_s.to_json
end end
end end
end
# Patch json serialization of Time Objects
class Time
# Returns a hash, that will be turned into a JSON object and represent this
# object.
# Note that this was done to prevent milliseconds from showing up in the JSON response thus breaking
# API compatibility for downstream clients.
def to_json(*)
'"' + utc().strftime("%Y-%m-%dT%H:%M:%SZ") + '"'
end end
end end
# these files must be required in order # these files must be required in order
require './api/search' require './api/search'
require './api/commentables' require './api/commentables'
...@@ -158,7 +126,7 @@ if RACK_ENV.to_s == "development" ...@@ -158,7 +126,7 @@ if RACK_ENV.to_s == "development"
end end
end end
error Moped::Errors::InvalidObjectId do error Mongo::Error::InvalidDocument do
error 400, [t(:requested_object_not_found)].to_json error 400, [t(:requested_object_not_found)].to_json
end end
...@@ -170,10 +138,10 @@ error ArgumentError do ...@@ -170,10 +138,10 @@ error ArgumentError do
error 400, [env['sinatra.error'].message].to_json error 400, [env['sinatra.error'].message].to_json
end end
CommentService.blocked_hashes = Content.mongo_session[:blocked_hash].find.select(hash: 1).each.map {|d| d["hash"]} CommentService.blocked_hashes = Content.mongo_client[:blocked_hash].find(nil, projection: {hash: 1}).map {|d| d["hash"]}
def get_db_is_master def get_db_is_master
Mongoid::Sessions.default.command(isMaster: 1) Mongoid::Clients.default.command(isMaster: 1)
end end
def get_es_status def get_es_status
...@@ -186,7 +154,7 @@ get '/heartbeat' do ...@@ -186,7 +154,7 @@ get '/heartbeat' do
db_ok = false db_ok = false
begin begin
res = get_db_is_master res = get_db_is_master
db_ok = ( res["ismaster"] == true and Integer(res["ok"]) == 1 ) db_ok = res.ok? && res.documents.first['ismaster'] == true
rescue rescue
end end
error 500, JSON.generate({"OK" => false, "check" => "db"}) unless db_ok error 500, JSON.generate({"OK" => false, "check" => "db"}) unless db_ok
......
...@@ -14,5 +14,8 @@ require "rack-timeout" ...@@ -14,5 +14,8 @@ require "rack-timeout"
use Rack::Timeout # Call as early as possible so rack-timeout runs before other middleware. use Rack::Timeout # Call as early as possible so rack-timeout runs before other middleware.
Rack::Timeout.timeout = 20 Rack::Timeout.timeout = 20
require "mongoid"
use Mongoid::QueryCache::Middleware
require './app' require './app'
run Sinatra::Application run Sinatra::Application
common: &default_client
options:
write:
w: 1
read:
mode: :primary
max_retries: <%= ENV['MONGOID_MAX_RETRIES'] || 1 %>
retry_interval: <%= ENV['MONGOID_RETRY_INTERVAL'] || 0 %>
timeout: <%= ENV['MONGOID_TIMEOUT'] || 0.5 %>
ssl: <%= ENV['MONGOID_USE_SSL'] || false %>
common_uri: &default_uri
uri: <%= ENV['MONGOHQ_URL'] %>
development: development:
sessions: clients:
default: default:
<<: *default_client
database: cs_comments_service_development database: cs_comments_service_development
hosts: hosts:
- localhost:27017 - localhost:27017
test: test:
sessions: clients:
default: default:
<<: *default_client
database: cs_comments_service_test database: cs_comments_service_test
hosts: hosts:
- localhost:27017 - localhost:27017
common: &default_session
uri: <%= ENV['MONGOHQ_URL'] %>
options:
skip_version_check: true
safe: true
consistency: strong
max_retries: <%= ENV['MONGOID_MAX_RETRIES'] || 1 %>
retry_interval: <%= ENV['MONGOID_RETRY_INTERVAL'] || 0 %>
timeout: <%= ENV['MONGOID_TIMEOUT'] || 0.5 %>
ssl: <%= ENV['MONGOID_USE_SSL'] || false %>
production: production:
sessions: clients:
default: default:
<<: *default_session <<: *default_uri
<<: *default_client
edgeprod: edgeprod:
sessions: clients:
default: default:
<<: *default_session <<: *default_uri
<<: *default_client
edgestage: edgestage:
sessions: clients:
default: default:
<<: *default_session <<: *default_uri
<<: *default_client
staging: staging:
sessions: clients:
default: default:
<<: *default_session <<: *default_uri
<<: *default_client
loadtest: loadtest:
sessions: clients:
default: default:
<<: *default_session <<: *default_uri
<<: *default_client
defaults: &defaults defaults: &defaults
use_utc: false use_utc: false
......
...@@ -13,5 +13,5 @@ after_fork do |server, worker| ...@@ -13,5 +13,5 @@ after_fork do |server, worker|
Signal.trap 'TERM' do Signal.trap 'TERM' do
puts 'Unicorn worker intercepting TERM and doing nothing. Waiting for master to send QUIT' puts 'Unicorn worker intercepting TERM and doing nothing. Waiting for master to send QUIT'
end end
::Mongoid.default_session.disconnect ::Mongoid.default_client.close
end end
...@@ -7,5 +7,5 @@ listen "unix:#{data_dir}/forum.sock", :backlog => 512 ...@@ -7,5 +7,5 @@ listen "unix:#{data_dir}/forum.sock", :backlog => 512
pid "#{data_dir}/forum_unicorn.pid" pid "#{data_dir}/forum_unicorn.pid"
after_fork do |server, worker| after_fork do |server, worker|
::Mongoid.default_session.disconnect ::Mongoid.default_client.close
end end
...@@ -11,5 +11,5 @@ data_dir = ENV['DATA_DIR'] || Dir.tmpdir ...@@ -11,5 +11,5 @@ data_dir = ENV['DATA_DIR'] || Dir.tmpdir
pid "#{data_dir}/forum_unicorn.pid" pid "#{data_dir}/forum_unicorn.pid"
after_fork do |server, worker| after_fork do |server, worker|
::Mongoid.default_session.disconnect ::Mongoid.default_client.close
end end
...@@ -190,10 +190,7 @@ helpers do ...@@ -190,10 +190,7 @@ helpers do
to_skip = (page - 1) * per_page to_skip = (page - 1) * per_page
has_more = false has_more = false
# batch_size is used to cap the number of documents we might load into memory at any given time # batch_size is used to cap the number of documents we might load into memory at any given time
# TODO: starting with Mongoid 3.1, you can just do comment_threads.batch_size(size).each() comment_threads.batch_size(CommentService.config["manual_pagination_batch_size"].to_i).each do |thread|
comment_threads.query.batch_size(CommentService.config["manual_pagination_batch_size"].to_i)
Mongoid.unit_of_work(disable: :current) do # this is to prevent Mongoid from memoizing every document we look at
comment_threads.each do |thread|
thread_key = thread._id.to_s thread_key = thread._id.to_s
if !read_dates.has_key?(thread_key) || read_dates[thread_key] < thread.last_activity_at if !read_dates.has_key?(thread_key) || read_dates[thread_key] < thread.last_activity_at
if skipped >= to_skip if skipped >= to_skip
...@@ -207,7 +204,7 @@ helpers do ...@@ -207,7 +204,7 @@ helpers do
end end
end end
end end
end
# The following trick makes frontend pagers work without recalculating # The following trick makes frontend pagers work without recalculating
# the number of all unread threads per user on every request (since the number # the number of all unread threads per user on every request (since the number
# of threads in a course could be tens or hundreds of thousands). It has the # of threads in a course could be tens or hundreds of thousands). It has the
...@@ -219,7 +216,7 @@ helpers do ...@@ -219,7 +216,7 @@ helpers do
# let the installed paginator library handle pagination # let the installed paginator library handle pagination
num_pages = [1, (comment_threads.count / per_page.to_f).ceil].max num_pages = [1, (comment_threads.count / per_page.to_f).ceil].max
page = [1, page].max page = [1, page].max
threads = comment_threads.page(page).per(per_page).to_a threads = comment_threads.paginate(:page => page, :per_page => per_page).to_a
end end
if threads.length == 0 if threads.length == 0
......
...@@ -5,6 +5,7 @@ class Comment < Content ...@@ -5,6 +5,7 @@ class Comment < Content
include Mongoid::Tree include Mongoid::Tree
include Mongoid::Timestamps include Mongoid::Timestamps
include Mongoid::MagicCounterCache include Mongoid::MagicCounterCache
include ActiveModel::MassAssignmentSecurity
voteable self, :up => +1, :down => -1 voteable self, :up => +1, :down => -1
...@@ -14,6 +15,7 @@ class Comment < Content ...@@ -14,6 +15,7 @@ class Comment < Content
field :endorsement, type: Hash field :endorsement, type: Hash
field :anonymous, type: Boolean, default: false field :anonymous, type: Boolean, default: false
field :anonymous_to_peers, type: Boolean, default: false field :anonymous_to_peers, type: Boolean, default: false
field :commentable_id, type: String
field :at_position_list, type: Array, default: [] field :at_position_list, type: Array, default: []
index({author_id: 1, course_id: 1}) index({author_id: 1, course_id: 1})
...@@ -101,10 +103,10 @@ class Comment < Content ...@@ -101,10 +103,10 @@ class Comment < Content
.merge("user_id" => author_id) .merge("user_id" => author_id)
.merge("username" => author_username) .merge("username" => author_username)
.merge("depth" => depth) .merge("depth" => depth)
.merge("closed" => comment_thread.nil? ? false : comment_thread.closed) # ditto .merge("closed" => comment_thread.nil? ? false : comment_thread.closed)
.merge("thread_id" => comment_thread_id) .merge("thread_id" => comment_thread_id)
.merge("parent_id" => parent_ids[-1]) .merge("parent_id" => parent_ids[-1])
.merge("commentable_id" => comment_thread.nil? ? nil : comment_thread.commentable_id) # ditto .merge("commentable_id" => comment_thread.nil? ? nil : comment_thread.commentable_id)
.merge("votes" => votes.slice(*%w[count up_count down_count point])) .merge("votes" => votes.slice(*%w[count up_count down_count point]))
.merge("abuse_flaggers" => abuse_flaggers) .merge("abuse_flaggers" => abuse_flaggers)
.merge("type" => "comment") .merge("type" => "comment")
...@@ -156,7 +158,7 @@ class Comment < Content ...@@ -156,7 +158,7 @@ class Comment < Content
private private
def set_thread_last_activity_at def set_thread_last_activity_at
self.comment_thread.update_attributes!(last_activity_at: Time.now.utc) self.comment_thread.update_attribute(:last_activity_at, Time.now.utc)
end end
end end
...@@ -5,6 +5,8 @@ require_relative 'content' ...@@ -5,6 +5,8 @@ require_relative 'content'
class CommentThread < Content class CommentThread < Content
include Mongoid::Timestamps include Mongoid::Timestamps
include Mongoid::Attributes::Dynamic
include ActiveModel::MassAssignmentSecurity
extend Enumerize extend Enumerize
voteable self, :up => +1, :down => -1 voteable self, :up => +1, :down => -1
...@@ -154,8 +156,8 @@ private ...@@ -154,8 +156,8 @@ private
# the last activity time on the thread. Therefore the callbacks would be mutually recursive and we end up with a # the last activity time on the thread. Therefore the callbacks would be mutually recursive and we end up with a
# 'SystemStackError'. The 'set' method skips callbacks and therefore bypasses this issue. # 'SystemStackError'. The 'set' method skips callbacks and therefore bypasses this issue.
self.comments.each do |comment| self.comments.each do |comment|
comment.set :endorsed, false comment.set(endorsed: false)
comment.set :endorsement, nil comment.set(endorsement: nil)
end end
end end
end end
......
class Notification class Notification
include Mongoid::Document include Mongoid::Document
include Mongoid::Timestamps include Mongoid::Timestamps
include ActiveModel::MassAssignmentSecurity
field :notification_type, type: String field :notification_type, type: String
field :info, type: Hash field :info, type: Hash
......
...@@ -12,7 +12,7 @@ class Subscription ...@@ -12,7 +12,7 @@ class Subscription
index({source_id: 1, source_type: 1}, {background: true}) index({source_id: 1, source_type: 1}, {background: true})
def to_hash def to_hash
as_document.slice(*%w[subscriber_id source_id source_type]) as_document.slice(*%w[subscriber_id source_id source_type]).merge("id" => _id)
end end
def subscriber def subscriber
......
...@@ -153,7 +153,8 @@ class ReadState ...@@ -153,7 +153,8 @@ class ReadState
field :last_read_times, type: Hash, default: {} field :last_read_times, type: Hash, default: {}
embedded_in :user embedded_in :user
validates :course_id, uniqueness: true, presence: true validates_presence_of :course_id
validates_uniqueness_of :course_id
def to_hash def to_hash
to_json to_json
......
...@@ -5,10 +5,10 @@ module ThreadUtils ...@@ -5,10 +5,10 @@ module ThreadUtils
# only threads which are endorsed will have entries, value will always be true. # only threads which are endorsed will have entries, value will always be true.
endorsed_threads = {} endorsed_threads = {}
thread_ids = threads.collect {|t| t._id} thread_ids = threads.collect {|t| t._id}
Comment.collection.aggregate( Comment.collection.aggregate([
{"$match" => {"comment_thread_id" => {"$in" => thread_ids}, "endorsed" => true}}, {"$match" => {"comment_thread_id" => {"$in" => thread_ids}, "endorsed" => true}},
{"$group" => {"_id" => "$comment_thread_id"}} {"$group" => {"_id" => "$comment_thread_id"}}
).each do |res| ]).each do |res|
endorsed_threads[res["_id"].to_s] = true endorsed_threads[res["_id"].to_s] = true
end end
endorsed_threads endorsed_threads
...@@ -26,7 +26,7 @@ module ThreadUtils ...@@ -26,7 +26,7 @@ module ThreadUtils
thread_key = t._id.to_s thread_key = t._id.to_s
if read_dates.has_key? thread_key if read_dates.has_key? thread_key
is_read = read_dates[thread_key] >= t.updated_at is_read = read_dates[thread_key] >= t.updated_at
unread_comment_count = Comment.collection.where( unread_comment_count = Comment.collection.find(
:comment_thread_id => t._id, :comment_thread_id => t._id,
:author_id => {"$ne" => user.id}, :author_id => {"$ne" => user.id},
:updated_at => {"$gte" => read_dates[thread_key]} :updated_at => {"$gte" => read_dates[thread_key]}
......
...@@ -43,9 +43,6 @@ describe "app" do ...@@ -43,9 +43,6 @@ describe "app" do
describe "flag a comment as abusive" do describe "flag a comment as abusive" do
it "create or update the abuse_flags on the comment" do it "create or update the abuse_flags on the comment" do
comment = Comment.first comment = Comment.first
# We get the count rather than just keeping the array, because the array
# will update as the Comment updates since the IdentityMap is enabled.
prev_abuse_flaggers_count = comment.abuse_flaggers.length prev_abuse_flaggers_count = comment.abuse_flaggers.length
create_comment_flag("#{comment.id}", User.first.id) create_comment_flag("#{comment.id}", User.first.id)
......
...@@ -7,6 +7,7 @@ describe "app" do ...@@ -7,6 +7,7 @@ describe "app" do
describe "comments" do describe "comments" do
before(:each) { init_without_subscriptions } before(:each) { init_without_subscriptions }
describe "GET /api/v1/comments/:comment_id" do describe "GET /api/v1/comments/:comment_id" do
it "returns JSON" do it "returns JSON" do
comment = Comment.first comment = Comment.first
...@@ -25,10 +26,10 @@ describe "app" do ...@@ -25,10 +26,10 @@ describe "app" do
retrieved["children"].should be_nil retrieved["children"].should be_nil
retrieved["votes"]["point"].should == comment.votes_point retrieved["votes"]["point"].should == comment.votes_point
retrieved["depth"].should == comment.depth retrieved["depth"].should == comment.depth
retrieved["parent_id"].should == comment.parent_ids[-1] retrieved["parent_id"].should == comment.parent_ids.map(&:to_s)[-1]
end end
it "retrieve information of a single comment with its sub comments" do it "retrieve information of a single comment with its sub comments" do
comment = Comment.first comment = Comment.order_by(_id: 'asc').first
get "/api/v1/comments/#{comment.id}", recursive: true get "/api/v1/comments/#{comment.id}", recursive: true
last_response.should be_ok last_response.should be_ok
retrieved = parse last_response.body retrieved = parse last_response.body
...@@ -56,6 +57,7 @@ describe "app" do ...@@ -56,6 +57,7 @@ describe "app" do
include_examples "unicode data" include_examples "unicode data"
end end
describe "PUT /api/v1/comments/:comment_id" do describe "PUT /api/v1/comments/:comment_id" do
def test_update_endorsed(true_val, false_val) def test_update_endorsed(true_val, false_val)
comment = Comment.first comment = Comment.first
...@@ -114,11 +116,13 @@ describe "app" do ...@@ -114,11 +116,13 @@ describe "app" do
comment = Comment.first comment = Comment.first
put "/api/v1/comments/#{comment.id}", body: text put "/api/v1/comments/#{comment.id}", body: text
last_response.should be_ok last_response.should be_ok
comment = Comment.find(comment.id)
comment.body.should == text comment.body.should == text
end end
include_examples "unicode data" include_examples "unicode data"
end end
describe "POST /api/v1/comments/:comment_id" do describe "POST /api/v1/comments/:comment_id" do
it "create a sub comment to the comment" do it "create a sub comment to the comment" do
comment = Comment.first.to_hash(recursive: true) comment = Comment.first.to_hash(recursive: true)
...@@ -154,6 +158,7 @@ describe "app" do ...@@ -154,6 +158,7 @@ describe "app" do
include_examples "unicode data" include_examples "unicode data"
end end
describe "DELETE /api/v1/comments/:comment_id" do describe "DELETE /api/v1/comments/:comment_id" do
it "delete the comment and its sub comments" do it "delete the comment and its sub comments" do
comment = Comment.first comment = Comment.first
......
...@@ -455,6 +455,8 @@ describe "app" do ...@@ -455,6 +455,8 @@ describe "app" do
last_response.should be_ok last_response.should be_ok
response_thread = parse last_response.body response_thread = parse last_response.body
response_thread["endorsed"].should == true response_thread["endorsed"].should == true
# re-request the thread from the database before checking it.
thread = CommentThread.find(thread.id)
check_thread_result_json(nil, thread, response_thread) check_thread_result_json(nil, thread, response_thread)
end end
...@@ -463,16 +465,11 @@ describe "app" do ...@@ -463,16 +465,11 @@ describe "app" do
# regression in which we used User.only(:id, :read_states). This worked # regression in which we used User.only(:id, :read_states). This worked
# before we included the identity map, but afterwards, the user was # before we included the identity map, but afterwards, the user was
# missing the username and was not refetched. # missing the username and was not refetched.
# BBEGGS - Note 8/4/2015: Identify map has been removed during the mongoid 4.x upgrade.
# Should no longer be an issue.
it "includes the username even if the thread is being marked as read for the thread author" do it "includes the username even if the thread is being marked as read for the thread author" do
thread = CommentThread.first thread = CommentThread.first
expected_username = thread.author.username expected_username = thread.author.username
# We need to clear the IdentityMap after getting the expected data to
# ensure that this spec fails when it should. If we don't do this, then
# in the cases where the User is fetched without its username, the spec
# won't fail because the User will already be in the identity map.
Mongoid::IdentityMap.clear
get "/api/v1/threads/#{thread.id}", {:user_id => thread.author_id, :mark_as_read => true} get "/api/v1/threads/#{thread.id}", {:user_id => thread.author_id, :mark_as_read => true}
last_response.should be_ok last_response.should be_ok
response_thread = parse last_response.body response_thread = parse last_response.body
...@@ -657,6 +654,7 @@ describe "app" do ...@@ -657,6 +654,7 @@ describe "app" do
thread = CommentThread.first thread = CommentThread.first
put "/api/v1/threads/#{thread.id}", body: text, title: text put "/api/v1/threads/#{thread.id}", body: text, title: text
last_response.should be_ok last_response.should be_ok
thread = CommentThread.find(thread.id)
thread.body.should == text thread.body.should == text
thread.title.should == text thread.title.should == text
end end
......
...@@ -45,7 +45,7 @@ describe "app" do ...@@ -45,7 +45,7 @@ describe "app" do
course1_threads.should_not == course2_threads course1_threads.should_not == course2_threads
end end
it "filters by group_id" do it "filters by group_id" do
group_thread = Commentable.find("question_1").comment_threads.first group_thread = Commentable.find("question_1").comment_threads.sort(_id: 1).first
threads = thread_result "question_1", group_id: 42 threads = thread_result "question_1", group_id: 42
threads.length.should == 2 threads.length.should == 2
group_thread.group_id = 43 group_thread.group_id = 43
...@@ -58,7 +58,7 @@ describe "app" do ...@@ -58,7 +58,7 @@ describe "app" do
threads.length.should == 2 threads.length.should == 2
end end
it "filters by group_ids" do it "filters by group_ids" do
group_thread = Commentable.find("question_1").comment_threads.first group_thread = Commentable.find("question_1").comment_threads.sort(_id: 1).first
group_thread.group_id = 42 group_thread.group_id = 42
group_thread.save! group_thread.save!
threads = thread_result "question_1", group_ids: "42,43" threads = thread_result "question_1", group_ids: "42,43"
......
...@@ -17,7 +17,7 @@ describe "app" do ...@@ -17,7 +17,7 @@ describe "app" do
random_string = (0...8).map{ ('a'..'z').to_a[rand(26)] }.join random_string = (0...8).map{ ('a'..'z').to_a[rand(26)] }.join
thread = CommentThread.new( thread = CommentThread.new(
title: "Test title", body: "elephant otter", course_id: "1", title: "Test title", body: "elephant otter", course_id: "1",
commentable_id: commentable.id, comments_text_dummy: random_string commentable_id: commentable.id, body: random_string
) )
thread.thread_type = :discussion thread.thread_type = :discussion
thread.author = user thread.author = user
...@@ -94,7 +94,8 @@ describe "app" do ...@@ -94,7 +94,8 @@ describe "app" do
subscription = Subscription.create({:subscriber_id => user._id.to_s, :source_id => thread._id.to_s}) subscription = Subscription.create({:subscriber_id => user._id.to_s, :source_id => thread._id.to_s})
comment = Comment.new(body: "dummy body text", course_id: "1", commentable_id: commentable.id) comment = Comment.new(body: "dummy body text", course_id: "1")
comment.commentable_id = commentable.id
comment.author = user comment.author = user
comment.comment_thread = thread comment.comment_thread = thread
comment.save! comment.save!
......
...@@ -42,7 +42,8 @@ describe "app" do ...@@ -42,7 +42,8 @@ describe "app" do
sleep 3 sleep 3
comment = Comment.new(body: random_string, course_id: "1", commentable_id: commentable.id) comment = Comment.new(body: random_string, course_id: "1")
comment.commentable_id = commentable.id
comment.author = author comment.author = author
comment.comment_thread = thread comment.comment_thread = thread
comment.save! comment.save!
......
...@@ -45,8 +45,11 @@ describe "app" do ...@@ -45,8 +45,11 @@ describe "app" do
context "db check" do context "db check" do
def test_db_check(response, is_success) def test_db_check(response, is_success)
db = double("db") db = double("db")
stub_const("Mongoid::Sessions", Class.new).stub(:default).and_return(db) stub_const("Mongoid::Clients", Class.new).stub(:default).and_return(db)
db.should_receive(:command).with({:isMaster => 1}).and_return(response) result = double('result')
result.stub(:ok?).and_return(response['ok'] == 1)
result.stub(:documents).and_return([response])
db.should_receive(:command).with({:isMaster => 1}).and_return(result)
get "/heartbeat" get "/heartbeat"
if is_success if is_success
last_response.status.should == 200 last_response.status.should == 200
...@@ -75,7 +78,7 @@ describe "app" do ...@@ -75,7 +78,7 @@ describe "app" do
it "reports failure when db command raises an error" do it "reports failure when db command raises an error" do
db = double("db") db = double("db")
stub_const("Mongoid::Sessions", Class.new).stub(:default).and_return(db) stub_const("Mongoid::Clients", Class.new).stub(:default).and_return(db)
db.should_receive(:command).with({:isMaster => 1}).and_raise(StandardError) db.should_receive(:command).with({:isMaster => 1}).and_raise(StandardError)
get "/heartbeat" get "/heartbeat"
last_response.status.should == 500 last_response.status.should == 500
......
...@@ -15,6 +15,9 @@ set :run, false ...@@ -15,6 +15,9 @@ set :run, false
set :raise_errors, true set :raise_errors, true
set :logging, false set :logging, false
Mongoid.logger.level = Logger::WARN
Mongo::Logger.logger.level = ENV["ENABLE_MONGO_DEBUGGING"] ? Logger::DEBUG : Logger::WARN
Delayed::Worker.delay_jobs = false Delayed::Worker.delay_jobs = false
def app def app
...@@ -53,7 +56,6 @@ RSpec.configure do |config| ...@@ -53,7 +56,6 @@ RSpec.configure do |config|
config.filter_run focus: true config.filter_run focus: true
config.run_all_when_everything_filtered = true config.run_all_when_everything_filtered = true
config.before(:each) do config.before(:each) do
Mongoid::IdentityMap.clear
DatabaseCleaner.clean DatabaseCleaner.clean
delete_es_index delete_es_index
create_es_index create_es_index
...@@ -73,12 +75,6 @@ def create_test_user(id) ...@@ -73,12 +75,6 @@ def create_test_user(id)
end end
def init_without_subscriptions def init_without_subscriptions
[Comment, CommentThread, User, Notification, Subscription, Activity, Delayed::Backend::Mongoid::Job].each(&:delete_all).each(&:remove_indexes).each(&:create_indexes)
Content.mongo_session[:blocked_hash].drop
delete_es_index
create_es_index
commentable = Commentable.new("question_1") commentable = Commentable.new("question_1")
users = (1..10).map{|id| create_test_user(id)} users = (1..10).map{|id| create_test_user(id)}
...@@ -158,18 +154,14 @@ def init_without_subscriptions ...@@ -158,18 +154,14 @@ def init_without_subscriptions
users[2,9].each {|user| user.vote(c, [:up, :down].sample)} users[2,9].each {|user| user.vote(c, [:up, :down].sample)}
end end
Content.mongo_session[:blocked_hash].insert(hash: Digest::MD5.hexdigest("blocked post")) Content.mongo_client[:blocked_hash].insert_one(hash: Digest::MD5.hexdigest("blocked post"))
# reload the global holding the blocked hashes # reload the global holding the blocked hashes
CommentService.blocked_hashes = Content.mongo_session[:blocked_hash].find.select(hash: 1).each.map {|d| d["hash"]} CommentService.blocked_hashes = Content.mongo_client[:blocked_hash].find(nil, projection: {hash: 1}).map do |d|
d["hash"]
end
end end
def init_with_subscriptions def init_with_subscriptions
[Comment, CommentThread, User, Notification, Subscription, Activity, Delayed::Backend::Mongoid::Job].each(&:delete_all).each(&:remove_indexes).each(&:create_indexes)
delete_es_index
create_es_index
user1 = create_test_user(1) user1 = create_test_user(1)
user2 = create_test_user(2) user2 = create_test_user(2)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment