Skip to content

Commit

Permalink
Added PostgreSQL support, closes slack-ruby#7.
Browse files Browse the repository at this point in the history
  • Loading branch information
dblock committed Mar 7, 2017
1 parent e18cd67 commit 5cbb5e9
Show file tree
Hide file tree
Showing 57 changed files with 667 additions and 163 deletions.
17 changes: 8 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,21 @@ language: ruby

cache: bundler

services:
- mongodb

matrix:
include:
- rvm: 2.3.1
script:
- bundle exec danger
- rvm: 2.3.1
- rvm: 2.3.0
- rvm: ruby-head
- rvm: jruby-head
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
env: DATABASE_ADAPTER=activerecord
services:
- postgresql
- rvm: 2.3.1
env: DATABASE_ADAPTER=mongoid
services:
- mongodb

before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"

3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
### Changelog

#### 0.5.1 (Next)
#### 0.6.0 (Next)

* [#38](https://github.com/slack-ruby/slack-ruby-bot-server/issues/7): Added ActiveRecord support - [@zachfeldman](https://github.com/zachfeldman), [@spencerldixon](https://github.com/spencerldixon), [@dblock](https://github.com/dblock).
* [#45](https://github.com/slack-ruby/slack-ruby-bot-server/pull/45): Updated grape-roar to 0.4.0 - [@swalberg](https://github.com/swalberg).
* Your contribution here.

Expand Down
14 changes: 14 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
source 'https://rubygems.org'

case ENV['DATABASE_ADAPTER']
when 'mongoid' then
gem 'mongoid'
gem 'kaminari-mongoid'
gem 'mongoid-scroll'
when 'activerecord' then
gem 'pg'
gem 'activerecord'
gem 'otr-activerecord'
gem 'cursor_pagination'
else
raise "Invalid DATABASE_ADAPTER #{database_adapter}."
end

gemspec

group :development, :test do
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@ Once a bot is registered, you can invite to a channel with `/invite @slackbotser

### Run Your Own

You can use the [sample application](sample_app) to bootstrap your project and start adding slack command handlers on top of this code.
You can use the [sample application](sample_app) to bootstrap your project and start adding slack command handlers on top of this code. A database is required to store teams.

Install [MongoDB](https://www.mongodb.org/downloads), required to store teams. We would like your help with [supporting other databases](https://github.com/slack-ruby/slack-ruby-bot-server/issues/12).
### MongoDB

Use MongoDB with [Mongoid](https://github.com/mongodb/mongoid) as ODM. Configure the database connection in `mongoid.yml`. See the [MongoDB example](sample_apps/sample_app_mongoid) for more information.

### ActiveRecord

Use ActiveRecord with, for example, PostgreSQL via [pg](https://github.com/ged/ruby-pg). Configure the database connection in `postgresql.yml`. See the [ActiveRecord example](sample_apps/sample_app_activerecord) for more information.

### Usage

[Create a New Application](https://api.slack.com/applications/new) on Slack.

Expand Down
11 changes: 6 additions & 5 deletions lib/slack-ruby-bot-server.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
require 'celluloid/current'
require 'kaminari/grape'
require 'mongoid-scroll'

require 'grape-swagger'
require 'slack-ruby-bot'
require 'slack-ruby-bot-server/server'
require 'slack-ruby-bot-server/config'

require 'slack-ruby-bot-server/ext'
require 'slack-ruby-bot-server/version'
require 'slack-ruby-bot-server/info'
require 'slack-ruby-bot-server/models'

require "slack-ruby-bot-server/config/database_adapters/#{SlackRubyBotServer::Config.database_adapter}.rb"

require 'slack-ruby-bot-server/api'
require 'slack-ruby-bot-server/app'
require 'slack-ruby-bot-server/server'
require 'slack-ruby-bot-server/config'
require 'slack-ruby-bot-server/service'
2 changes: 1 addition & 1 deletion lib/slack-ruby-bot-server/api/endpoints/teams_endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class TeamsEndpoint < Grape::API
get do
teams = Team.all
teams = teams.active if params[:active]
teams = paginate_and_sort_by_cursor(teams, default_sort_order: '-_id')
teams = paginate_and_sort_by_cursor(teams, default_sort_order: '-id')
present teams, with: Presenters::TeamsPresenter
end

Expand Down
49 changes: 31 additions & 18 deletions lib/slack-ruby-bot-server/api/helpers/cursor_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,41 @@ module CursorHelpers
# returns a hash:
# results: (paginated collection subset)
# next: (cursor to the next page)
def paginate_by_cursor(coll, &_block)
raise 'Both cursor and offset parameters are present, these are mutually exclusive.' if params.key?(:offset) && params.key?(:cursor)
results = { results: [], next: nil }
size = (params[:size] || 10).to_i
if params.key?(:offset)
skip = params[:offset].to_i
coll = coll.skip(skip)
if SlackRubyBotServer::Config.mongoid?
def paginate_by_cursor(coll, _options)
raise 'Both cursor and offset parameters are present, these are mutually exclusive.' if params.key?(:offset) && params.key?(:cursor)
results = { results: [], next: nil }
coll = coll.skip(params[:offset].to_i) if params.key?(:offset)
size = (params[:size] || 10).to_i
coll = coll.limit(size)
coll.scroll(params[:cursor]) do |record, next_cursor|
results[:results] << record if record
results[:next] = next_cursor.to_s
break if results[:results].count >= size
end
results[:total_count] = coll.count if params[:total_count] && coll.respond_to?(:count)
results
end
# some items may be skipped with a block
query = block_given? ? coll : coll.limit(size)
query.scroll(params[:cursor]) do |record, next_cursor|
record = yield(record) if block_given?
results[:results] << record if record
results[:next] = next_cursor.to_s
break if results[:results].count >= size
elsif SlackRubyBotServer::Config.activerecord?
def paginate_by_cursor(coll, options)
raise 'Both cursor and offset parameters are present, these are mutually exclusive.' if params.key?(:offset) && params.key?(:cursor)
results = { results: [], next: nil }
size = (params[:size] || 10).to_i
results[:total_count] = coll.count(:all) if params[:total_count]
coll = coll.offset(params[:offset].to_i) if params.key?(:offset)
sort_options = {}
sort_order(options).each do |order|
sort_options[order[:column]] = { reverse: true } if order[:direction] == :desc
end
coll = coll.cursor(params[:cursor], columns: sort_options).per(size)
results[:results] = coll.to_a
results[:next] = coll.next_cursor.to_s unless coll.last_page?
results
end
results[:total_count] = coll.count if params[:total_count] && coll.respond_to?(:count)
results
end

def paginate_and_sort_by_cursor(coll, options = {}, &block)
Hashie::Mash.new(paginate_by_cursor(sort(coll, options), &block))
def paginate_and_sort_by_cursor(coll, options = {})
Hashie::Mash.new(paginate_by_cursor(sort(coll, options), options))
end
end
end
Expand Down
22 changes: 12 additions & 10 deletions lib/slack-ruby-bot-server/api/helpers/error_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ module ErrorHelpers
rack_response(error.to_json, 400)
end
# rescue document validation errors into detail json
rescue_from Mongoid::Errors::Validations do |e|
backtrace = e.backtrace[0..5].join("\n ")
Middleware.logger.warn "#{e.class.name}: #{e.message}\n #{backtrace}"
rack_response({
type: 'param_error',
message: e.document.errors.full_messages.uniq.join(', ') + '.',
detail: e.document.errors.messages.each_with_object({}) do |(k, v), h|
h[k] = v.uniq
end
}.to_json, 400)
if SlackRubyBotServer::Config.mongoid?
rescue_from Mongoid::Errors::Validations do |e|
backtrace = e.backtrace[0..5].join("\n ")
Middleware.logger.warn "#{e.class.name}: #{e.message}\n #{backtrace}"
rack_response({
type: 'param_error',
message: e.document.errors.full_messages.uniq.join(', ') + '.',
detail: e.document.errors.messages.each_with_object({}) do |(k, v), h|
h[k] = v.uniq
end
}.to_json, 400)
end
end
rescue_from Grape::Exceptions::Validation do |e|
backtrace = e.backtrace[0..5].join("\n ")
Expand Down
6 changes: 5 additions & 1 deletion lib/slack-ruby-bot-server/api/helpers/sort_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ def route_sort
def sort(coll, options = {})
sort_order = sort_order(options)
unless sort_order.empty?
if coll.respond_to?(:asc) && coll.respond_to?(:desc)
if coll.respond_to?(:order)
sort_order.each do |s|
coll = coll.order(s[:column] => s[:direction])
end
elsif coll.respond_to?(:asc) && coll.respond_to?(:desc)
sort_order.each do |s|
coll = coll.send(s[:direction], s[:column])
end
Expand Down
7 changes: 6 additions & 1 deletion lib/slack-ruby-bot-server/api/middleware.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
%w(rack/cors rack-rewrite rack-server-pages).each { |l| require l }
require 'rack/cors'
require 'rack-rewrite'
require 'rack-server-pages'
require 'otr-activerecord' if SlackRubyBotServer::Config.activerecord?

module SlackRubyBotServer
module Api
Expand All @@ -12,6 +15,8 @@ def self.logger

def self.instance
@instance ||= Rack::Builder.new do
use OTR::ActiveRecord::ConnectionManagement if SlackRubyBotServer::Config.activerecord?

use Rack::Cors do
allow do
origins '*'
Expand Down
8 changes: 7 additions & 1 deletion lib/slack-ruby-bot-server/api/presenters/status_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ module StatusPresenter
property :ping

def ping
team = Team.asc(:_id).first
if SlackRubyBotServer::Config.mongoid?
team = Team.asc(:_id).first
elsif SlackRubyBotServer::Config.activerecord?
team = Team.last
else
raise 'Unsupported database driver.'
end
return unless team
team.ping!
end
Expand Down
27 changes: 4 additions & 23 deletions lib/slack-ruby-bot-server/app.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
module SlackRubyBotServer
class App
def prepare!
silence_loggers!
check_mongodb_provider!
check_database!
create_indexes!
init_database!
mark_teams_active!
migrate_from_single_team!
update_team_name_and_id!
Expand All @@ -25,29 +23,12 @@ def logger
end
end

def silence_loggers!
Mongoid.logger.level = Logger::INFO
Mongo::Logger.logger.level = Logger::INFO
end

def check_mongodb_provider!
return unless ENV['RACK_ENV'] == 'production'
unless ENV['MONGO_URL'] || ENV['MONGOHQ_URI'] || ENV['MONGODB_URI'] || ENV['MONGOLAB_URI']
raise "Missing ENV['MONGO_URL'], ENV['MONGOHQ_URI'], ENV['MONGODB_URI'], or ENV['MONGOLAB_URI']."
end
end

def check_database!
rc = Mongoid.default_client.command(ping: 1)
return if rc && rc.ok?
raise rc.documents.first['error'] || 'Unexpected error.'
rescue Exception => e
warn "Error connecting to MongoDB: #{e.message}"
raise e
SlackRubyBotServer::DatabaseAdapter.check!
end

def create_indexes!
::Mongoid::Tasks::Database.create_indexes
def init_database!
SlackRubyBotServer::DatabaseAdapter.init!
end

def mark_teams_active!
Expand Down
16 changes: 16 additions & 0 deletions lib/slack-ruby-bot-server/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,25 @@ module Config
extend self

attr_accessor :server_class
attr_accessor :database_adapter

def reset!
self.server_class = SlackRubyBotServer::Server
self.database_adapter = if defined?(::Mongoid)
:mongoid
elsif defined?(::ActiveRecord)
:activerecord
else
raise 'One of "mongoid" or "activerecord" is required.'
end
end

def activerecord?
database_adapter == :activerecord
end

def mongoid?
database_adapter == :mongoid
end

reset!
Expand Down
27 changes: 27 additions & 0 deletions lib/slack-ruby-bot-server/config/database_adapters/activerecord.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require 'slack-ruby-bot-server/models/team/activerecord.rb'

module SlackRubyBotServer
module DatabaseAdapter
def self.check!
ActiveRecord::Base.connection_pool.with_connection(&:active?)
raise 'Unexpected error.' unless ActiveRecord::Base.connected?
rescue StandardError => e
warn "Error connecting to PostgreSQL: #{e.message}"
raise e
end

def self.init!
return if ActiveRecord::Base.connection.tables.include?('teams')
ActiveRecord::Base.connection.create_table :teams do |t|
t.string :team_id
t.string :name
t.string :domain
t.string :token
t.boolean :active, default: true
t.timestamps
end
end
end
end

::Boolean = Virtus::Attribute::Boolean
24 changes: 24 additions & 0 deletions lib/slack-ruby-bot-server/config/database_adapters/mongoid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'slack-ruby-bot-server/models/team/mongoid.rb'
require 'kaminari/grape'
require 'mongoid-scroll'

module SlackRubyBotServer
module DatabaseAdapter
def self.check!
rc = Mongoid.default_client.command(ping: 1)
return if rc && rc.ok?
raise rc.documents.first['error'] || 'Unexpected error.'
rescue StandardError => e
warn "Error connecting to MongoDB: #{e.message}"
raise e
end

def self.init!
# create indexes
::Mongoid::Tasks::Database.create_indexes
# silence loggers
Mongoid.logger.level = Logger::INFO
Mongo::Logger.logger.level = Logger::INFO
end
end
end
Loading

0 comments on commit 5cbb5e9

Please sign in to comment.