grape-gen 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/bin/grape-gen +5 -0
- data/grape-scaffold.gemspec +24 -0
- data/lib/grape/generate/version.rb +5 -0
- data/lib/grape/generate.rb +91 -0
- data/template/Gemfile.tt +95 -0
- data/template/Guardfile +20 -0
- data/template/abilities/api_ability.rb +15 -0
- data/template/api/api_app.rb +85 -0
- data/template/api/mounts/auth.rb.tt +42 -0
- data/template/api/mounts/profile.rb.tt +24 -0
- data/template/config/application.yml.tt +33 -0
- data/template/config/boot.rb.tt +85 -0
- data/template/config/boot_faye.rb +22 -0
- data/template/config/boot_sidekiq.rb.tt +33 -0
- data/template/config/boot_spec.rb.tt +85 -0
- data/template/config/database.rb.tt +6 -0
- data/template/config/database.yml.tt +21 -0
- data/template/config/initializers/carrierwave.rb +3 -0
- data/template/config/initializers/core_ext.rb +14 -0
- data/template/config/initializers/em-patches/carrierwave.rb +13 -0
- data/template/config/initializers/em-patches/faye.rb +4 -0
- data/template/config/initializers/em-patches/lazy_evaluated_pool.rb +35 -0
- data/template/config/initializers/em-patches/mandrill.rb +45 -0
- data/template/config/initializers/em-patches/redis.rb +13 -0
- data/template/config/initializers/em-patches/redis_lazy_evaluated_pool.rb +19 -0
- data/template/config/initializers/em-patches/sidekiq.rb +46 -0
- data/template/config/initializers/em-patches/tire.rb +8 -0
- data/template/config/initializers/faye.rb +3 -0
- data/template/config/initializers/grape.rb +11 -0
- data/template/config/initializers/logging.rb +38 -0
- data/template/config/initializers/mandrill.rb +1 -0
- data/template/config/initializers/patches/redis_namespace.rb +96 -0
- data/template/config/initializers/sidekiq.rb +15 -0
- data/template/config/initializers/tire.rb +6 -0
- data/template/config/initializers/workflow.rb +31 -0
- data/template/config/logging.yml.tt +39 -0
- data/template/config/settings.rb +24 -0
- data/template/config/sidekiq.yml.tt +22 -0
- data/template/config.ru.tt +8 -0
- data/template/faye.ru +55 -0
- data/template/jobs/pong_time.rb +11 -0
- data/template/lib/faye_auth_extension.rb +61 -0
- data/template/lib/faye_publisher.rb +63 -0
- data/template/lib/mongoid/tire_plugin.rb +17 -0
- data/template/lib/warden/token_strategy.rb +18 -0
- data/template/mailers/registration_mailer.rb +17 -0
- data/template/models/user.rb.tt +75 -0
- data/template/public/faye.html +36 -0
- data/template/search_indexes/search_index.rb +60 -0
- data/template/search_indexes/user_index.rb +20 -0
- data/template/spec/api/mounts/auth_spec.rb.tt +37 -0
- data/template/spec/factories/user.rb +8 -0
- data/template/spec/spec_helper.rb.tt +132 -0
- data/template/uploaders/avatar_uploader.rb +23 -0
- data/template/views/v1/user/profile.json.jbuilder.tt +4 -0
- metadata +147 -0
data/template/faye.ru
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative 'config/boot_faye'
|
2
|
+
|
3
|
+
Faye::WebSocket.load_adapter('thin')
|
4
|
+
Faye.logger = Logging.logger[:faye]
|
5
|
+
|
6
|
+
class ServerClientSecretAuth
|
7
|
+
def initialize(secret)
|
8
|
+
@secret = secret
|
9
|
+
end
|
10
|
+
def outgoing(message, callback)
|
11
|
+
if message['channel'] =~ %r{^/meta}
|
12
|
+
callback.(message)
|
13
|
+
return message
|
14
|
+
end
|
15
|
+
|
16
|
+
message['ext'] ||= {}
|
17
|
+
# Set the auth token
|
18
|
+
message['ext']['faye_server_secret'] = @secret
|
19
|
+
|
20
|
+
# Carry on and send the message to the server
|
21
|
+
callback.(message)
|
22
|
+
message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
faye_adapter_settings = {
|
27
|
+
mount: '/', timeout: 180,
|
28
|
+
extensions: [Faye::AuthExtension.new(ApplicationSettings.faye.server_secret)]
|
29
|
+
}
|
30
|
+
|
31
|
+
faye_adapter_settings[:engine] = {
|
32
|
+
type: Faye::Redis,
|
33
|
+
uri: ApplicationSettings.faye.redis.url
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
bayeux = Faye::RackAdapter.new(faye_adapter_settings)
|
38
|
+
|
39
|
+
faye_client = bayeux.get_client
|
40
|
+
faye_client.add_extension(ServerClientSecretAuth.new(ApplicationSettings.faye.server_secret))
|
41
|
+
|
42
|
+
EventMachine.schedule do
|
43
|
+
redis_connection = EventMachine::Hiredis.connect(ApplicationSettings.faye.redis[:url])
|
44
|
+
redis_connection = Redis::Namespace.new(ApplicationSettings.faye.redis[:namespace], redis: redis_connection) if ApplicationSettings.faye.redis[:namespace]
|
45
|
+
|
46
|
+
pop = Proc.new do
|
47
|
+
redis_connection.blpop('faye.messages',1).callback do |list, msg|
|
48
|
+
faye_client.publish(*MultiJson.load(msg).values_at('channel','payload')) if msg
|
49
|
+
EventMachine.next_tick(pop)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
EventMachine.next_tick(pop)
|
53
|
+
end
|
54
|
+
|
55
|
+
run bayeux
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'warden/token_strategy'
|
2
|
+
|
3
|
+
module Faye
|
4
|
+
class AuthExtension
|
5
|
+
def initialize(server_secret)
|
6
|
+
@server_secret = server_secret
|
7
|
+
end
|
8
|
+
|
9
|
+
def subscribe_authorized?(env, channel)
|
10
|
+
case channel
|
11
|
+
when
|
12
|
+
'/user/registered', '/time'
|
13
|
+
return true
|
14
|
+
when %r{^/user/([0-9]+)}
|
15
|
+
strategy = TokenStrategy.new(env)
|
16
|
+
return false unless strategy.valid? and (strategy.authenticate! == :success)
|
17
|
+
strategy.user.id == $1.to_i
|
18
|
+
else
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def incoming(message, callback)
|
24
|
+
# Let non-subscribe messages through
|
25
|
+
if message['ext'] && (message['ext']['faye_server_secret'] == @server_secret)
|
26
|
+
message.delete('ext')
|
27
|
+
callback.call(message)
|
28
|
+
return message
|
29
|
+
end
|
30
|
+
|
31
|
+
unless message['channel'] =~ %r{^/meta}
|
32
|
+
if message['ext'].nil? || (message['ext']['faye_server_secret'] != @server_secret)
|
33
|
+
message['error'] = 'Unauthorized'
|
34
|
+
end
|
35
|
+
|
36
|
+
callback.call(message)
|
37
|
+
return message
|
38
|
+
end
|
39
|
+
|
40
|
+
unless message['channel'] == '/meta/subscribe'
|
41
|
+
callback.call(message)
|
42
|
+
return message
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get subscribed channel and auth token
|
46
|
+
subscription = message['subscription']
|
47
|
+
|
48
|
+
message['ext'] ||= {}
|
49
|
+
|
50
|
+
env = {
|
51
|
+
'HTTP_X_AUTHORIZE' => message['ext']['X-Authorize']
|
52
|
+
}
|
53
|
+
|
54
|
+
message['error'] = 'Unauthorized' unless subscribe_authorized?(Hashie::Mash.new(env), subscription)
|
55
|
+
|
56
|
+
callback.call(message)
|
57
|
+
|
58
|
+
message
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
require 'em-synchrony/connection_pool'
|
3
|
+
|
4
|
+
module FayePublisher
|
5
|
+
FakePublishing = Struct.new(:channel, :payload)
|
6
|
+
class << self
|
7
|
+
def fake!
|
8
|
+
@fake = true
|
9
|
+
end
|
10
|
+
|
11
|
+
def redis=(arg)
|
12
|
+
case arg
|
13
|
+
when
|
14
|
+
::ConnectionPool,
|
15
|
+
EventMachine::Synchrony::ConnectionPool # Or its descendants
|
16
|
+
@redis = arg
|
17
|
+
else
|
18
|
+
@_config = arg
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def redis
|
23
|
+
@redis ||= begin
|
24
|
+
config = ApplicationSettings.faye.redis
|
25
|
+
ConnectionPool::Wrapper.new(size: config[:size] || 10) do
|
26
|
+
connection = Redis.new(url: config[:url])
|
27
|
+
connection = Redis::Namespace.new(config[:namespace], redis: connection) if config[:namespace]
|
28
|
+
connection
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def publish(channel, payload)
|
34
|
+
if @fake
|
35
|
+
fake_publishings.push(FakePublishing.new(channel, payload))
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
redis.rpush(
|
39
|
+
'faye.messages',
|
40
|
+
MultiJson.dump(
|
41
|
+
channel: channel,
|
42
|
+
payload: payload
|
43
|
+
)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def configured?; @connection || @_config end
|
48
|
+
|
49
|
+
def publishings
|
50
|
+
raise 'FayePublisher is not configured in fake! mode' unless @fake
|
51
|
+
fake_publishings
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear_publishings
|
55
|
+
@fake_publishings.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def fake_publishings
|
60
|
+
@fake_publishings ||= []
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module TirePlugin
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
after_save :tire_reindex
|
6
|
+
after_destroy :tire_remove
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def tire_reindex
|
11
|
+
SearchIndex.index(self)
|
12
|
+
end
|
13
|
+
def tire_remove
|
14
|
+
SearchIndex.remove(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class TokenStrategy < Warden::Strategies::Base
|
2
|
+
def store?; false end
|
3
|
+
def valid?
|
4
|
+
env['HTTP_X_AUTHORIZE']
|
5
|
+
end
|
6
|
+
def access_token_type
|
7
|
+
:public
|
8
|
+
end
|
9
|
+
def authenticate!
|
10
|
+
user = Models::User.find_by(token: env['HTTP_X_AUTHORIZE'])
|
11
|
+
|
12
|
+
if user.nil?
|
13
|
+
fail!(:no_access)
|
14
|
+
else
|
15
|
+
success!(user)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class RegistrationMailer < MandrillMailer::TemplateMailer
|
2
|
+
default from: 'welcome@yourdomain.com'
|
3
|
+
|
4
|
+
def registered(user)
|
5
|
+
mandrill_mail(
|
6
|
+
template: 'registration',
|
7
|
+
subject: 'Welcome',
|
8
|
+
to: user.email,
|
9
|
+
vars: {
|
10
|
+
'USER_NAME' => user.display_name,
|
11
|
+
'CONFIRMATION_LINK' => user.email_approvement_code
|
12
|
+
},
|
13
|
+
important: true,
|
14
|
+
inline_css: true
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Models
|
2
|
+
class User
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
include Mongoid::Paranoia
|
6
|
+
<%- if @es -%>
|
7
|
+
include Mongoid::TirePlugin
|
8
|
+
<%- end -%>
|
9
|
+
include BCrypt
|
10
|
+
include Workflow
|
11
|
+
|
12
|
+
field :display_name, type: String
|
13
|
+
field :email_submit_code, type: String
|
14
|
+
field :email
|
15
|
+
field :password_hash, type: String
|
16
|
+
field :role, type: Symbol, default: :guest
|
17
|
+
field :banned, type: Boolean
|
18
|
+
field :banned_at, type: DateTime
|
19
|
+
field :email_approvement_code, type: String
|
20
|
+
field :workflow_state, type: String, default: 'registered'
|
21
|
+
field :token, type: String
|
22
|
+
|
23
|
+
validates_presence_of :display_name, :email, :role
|
24
|
+
validates_uniqueness_of :email
|
25
|
+
<%- if @carrierwave-%>
|
26
|
+
mount_uploader :avatar, Uploaders::Avatar
|
27
|
+
<%- end -%>
|
28
|
+
workflow do
|
29
|
+
state :registered do
|
30
|
+
event :email_approved, transition_to: :approved
|
31
|
+
end
|
32
|
+
|
33
|
+
state :approved do
|
34
|
+
event :ban, transition_to: :banned
|
35
|
+
end
|
36
|
+
|
37
|
+
state :banned do
|
38
|
+
event :unban, transition_to: :approved
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def email_approved
|
43
|
+
self.email_approvement_code = nil
|
44
|
+
self.generate_token!
|
45
|
+
end
|
46
|
+
|
47
|
+
def password=(value)
|
48
|
+
@password = Password.create(value)
|
49
|
+
self.password_hash = @password
|
50
|
+
end
|
51
|
+
|
52
|
+
def password
|
53
|
+
@password ||= Password.new(self.password_hash)
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate_approvement_code!
|
57
|
+
hash_source = '%s-%s-%s'%[self.email, self.display_name, Time.now]
|
58
|
+
self.email_approvement_code = BCrypt::Password.create(hash_source, cost: 3).to_s[8..-1]
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate_token!
|
62
|
+
hash_source = '%s-%s-%s'%[self.email, self.display_name, Time.now]
|
63
|
+
self.token = BCrypt::Password.create(hash_source, cost: 3).to_s[8..-1]
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
def register(fields)
|
68
|
+
user = new(fields)
|
69
|
+
user.generate_approvement_code!
|
70
|
+
user.save!
|
71
|
+
user
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<title></title>
|
6
|
+
<link rel="stylesheet" href="style.css" />
|
7
|
+
<!--[if IE]>
|
8
|
+
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
9
|
+
<![endif]-->
|
10
|
+
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
|
11
|
+
<script src="http://localhost:9393/faye/client.js"></script>
|
12
|
+
|
13
|
+
<script type="text/javascript">
|
14
|
+
|
15
|
+
|
16
|
+
$(function(){
|
17
|
+
var $body = $('body'), client = new Faye.Client('http://localhost:9393/faye/');
|
18
|
+
|
19
|
+
function subscriptionActive(subscription){
|
20
|
+
$body.append('<p>Subscription active '+ subscription +'</p>');
|
21
|
+
}
|
22
|
+
|
23
|
+
client.subscribe('/time', function(data){
|
24
|
+
$body.prepend('<p>Current time is '+ data +'</p>');
|
25
|
+
}).then(subscriptionActive.bind(null, '/time'));
|
26
|
+
|
27
|
+
client.subscribe('/user/registered', function(data){
|
28
|
+
$body.prepend('<p>Please, welcome '+ data.display_name +'</p>');
|
29
|
+
}).then(subscriptionActive.bind(null, '/user/registered'));
|
30
|
+
});
|
31
|
+
</script>
|
32
|
+
</head>
|
33
|
+
<body>
|
34
|
+
|
35
|
+
</body>
|
36
|
+
</html>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class SearchIndex < Tire::Index
|
2
|
+
NoIndexForModel = Class.new(StandardError)
|
3
|
+
|
4
|
+
def initialize(name)
|
5
|
+
super("#{RACK_ENV}_#{name}")
|
6
|
+
end
|
7
|
+
|
8
|
+
def type
|
9
|
+
@_type ||= model.to_s.underscore.gsub('/','_')
|
10
|
+
end
|
11
|
+
|
12
|
+
def model
|
13
|
+
"Models::#{self.class.name.chomp('Index')}".constantize
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def create_indexes!
|
18
|
+
indexes.each do |index|
|
19
|
+
index.delete
|
20
|
+
index.create
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def index(instance)
|
25
|
+
index = index_by_klass(instance.class)
|
26
|
+
index.store(instance)
|
27
|
+
rescue NoIndexForModel
|
28
|
+
# ignored
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove(instance)
|
32
|
+
index = index_by_klass(instance.class)
|
33
|
+
index.remove(instance)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reindex!
|
37
|
+
indexes.each do |index_instance|
|
38
|
+
index_instance.delete
|
39
|
+
index_instance.create
|
40
|
+
index_instance.model.all.each do |model_instance|
|
41
|
+
index(model_instance)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def index_by_klass(model_klass)
|
48
|
+
@_cache ||= {}
|
49
|
+
@_cache[model_klass] and (return @_cache[model_klass])
|
50
|
+
idx = indexes.find{|idx| idx.model == model_klass}
|
51
|
+
raise NoIndexForModel unless idx
|
52
|
+
@_cache[model_klass] = idx
|
53
|
+
idx
|
54
|
+
end
|
55
|
+
|
56
|
+
def indexes
|
57
|
+
@_indexes ||= ObjectSpace.each_object(Class).select{ |klass| klass < self }.map(&:new)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class UserIndex < SearchIndex
|
2
|
+
def initialize
|
3
|
+
super('users')
|
4
|
+
end
|
5
|
+
|
6
|
+
def create
|
7
|
+
super(
|
8
|
+
mappings: {
|
9
|
+
type => {
|
10
|
+
# Your mappings here
|
11
|
+
}
|
12
|
+
}
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def store(instance)
|
17
|
+
index_data = instance.attributes
|
18
|
+
super(index_data)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rspec_api_documentation/dsl'
|
3
|
+
|
4
|
+
resource 'Authorization', focus: true do
|
5
|
+
<% if @mandrill %>
|
6
|
+
before(:each) { MandrillMailer.deliveries.clear }
|
7
|
+
<% end %>
|
8
|
+
post '/api/auth/register' do
|
9
|
+
example 'New user registration' do
|
10
|
+
new_user = FactoryGirl.attributes_for(:user).pick(:display_name, :email)
|
11
|
+
|
12
|
+
do_request(new_user)
|
13
|
+
expect(client.response).to succeed
|
14
|
+
<% if @mandrill %>
|
15
|
+
email = MandrillMailer::deliveries.detect { |mail|
|
16
|
+
mail.template_name == 'registration' &&
|
17
|
+
mail.message['to'].any? { |to| to['email'] == new_user[:email] }
|
18
|
+
}
|
19
|
+
<% end %>
|
20
|
+
expect(email).to_not be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
post '/api/auth/approve_email' do
|
25
|
+
example 'User email activation' do
|
26
|
+
user = FactoryGirl.create(:user)
|
27
|
+
user.generate_approvement_code!
|
28
|
+
user.save!
|
29
|
+
do_request(email: user.email, email_approvement_code: user.email_approvement_code)
|
30
|
+
expect(client.response).to succeed
|
31
|
+
expect(response_body).to include('display_name', 'email')
|
32
|
+
<% if @faye %>
|
33
|
+
expect(FayePublisher.publishings.length).to eq(1)
|
34
|
+
<% end %>
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spork'
|
3
|
+
require 'rack/test'
|
4
|
+
require 'rack/test/methods'
|
5
|
+
require 'rspec'
|
6
|
+
require 'rspec_api_documentation'
|
7
|
+
<% if @mandrill %>
|
8
|
+
require 'mandrill_mailer/offline'
|
9
|
+
<% end %>
|
10
|
+
RACK_ENV = 'test' unless defined?(RACK_ENV)
|
11
|
+
|
12
|
+
class TestApiClient < RspecApiDocumentation::RackTestClient
|
13
|
+
def response_body
|
14
|
+
body = JSON.load(last_response.body)
|
15
|
+
case body
|
16
|
+
when Array
|
17
|
+
body.map!{|item| Hashie::Mash.new(item) }
|
18
|
+
else
|
19
|
+
body
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def response; last_response end
|
24
|
+
end
|
25
|
+
|
26
|
+
module RspecApiDocumentation::DSL
|
27
|
+
module Resource
|
28
|
+
def client
|
29
|
+
@client ||= TestApiClient.new(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Spork.prefork do
|
35
|
+
require File.expand_path(File.dirname(__FILE__) + '/../config/boot_spec')
|
36
|
+
require 'database_cleaner'
|
37
|
+
<% if @sidekiq %>
|
38
|
+
Sidekiq::Testing.fake!
|
39
|
+
<% end %>
|
40
|
+
<% if @faye %>
|
41
|
+
FayePublisher.fake!
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<% if @dev_reload %>
|
45
|
+
Grape::RackBuilder.setup do
|
46
|
+
force_reloading true
|
47
|
+
end
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
RspecApiDocumentation.configure do |config|
|
51
|
+
config.app = RACK_APPLICATION
|
52
|
+
end
|
53
|
+
|
54
|
+
RSpec.configure do |conf|
|
55
|
+
FactoryGirl.reload
|
56
|
+
|
57
|
+
DatabaseCleaner.orm = :mongoid
|
58
|
+
DatabaseCleaner.strategy = :truncation
|
59
|
+
|
60
|
+
conf.include Rack::Test::Methods
|
61
|
+
|
62
|
+
conf.around(:each) do |example|
|
63
|
+
Logging.mdc.clear
|
64
|
+
SearchIndex.create_indexes!
|
65
|
+
DatabaseCleaner.cleaning { example.run }
|
66
|
+
Logging.mdc.clear
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def app(app = nil, &blk)
|
71
|
+
@app ||= block_given? ? app.instance_eval(&blk) : app
|
72
|
+
@app ||= RACK_APPLICATION
|
73
|
+
end
|
74
|
+
|
75
|
+
RSpec::Matchers.define :succeed do |valid_codes = nil|
|
76
|
+
match do |*args|
|
77
|
+
case valid_codes
|
78
|
+
when Integer
|
79
|
+
actual.status == valid_codes
|
80
|
+
when Array
|
81
|
+
valid_codes.include?(actual.status)
|
82
|
+
else
|
83
|
+
(actual.status == 200) || (actual.status == 201)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
match_when_negated do |*args|
|
88
|
+
case valid_codes
|
89
|
+
when Integer
|
90
|
+
actual.status == valid_codes
|
91
|
+
when Array
|
92
|
+
valid_codes.include?(actual.status)
|
93
|
+
else
|
94
|
+
actual.status > 201
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
failure_message do |actual|
|
99
|
+
case valid_codes
|
100
|
+
when Integer
|
101
|
+
"expected that #{actual} succeed with code #{valid_codes}, but got #{actual.status} error:\n#{actual.body}"
|
102
|
+
when Array
|
103
|
+
"expected that #{actual} succeed with one of #{valid_codes}, but got #{actual.status} error:\n#{actual.body}"
|
104
|
+
else
|
105
|
+
"expected that #{actual} succeed, but got #{actual.status} error:\n#{actual.body}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
failure_message_when_negated do |actual|
|
110
|
+
case valid_codes
|
111
|
+
when Integer
|
112
|
+
"expected that #{actual} fails with code #{valid_codes}, but got #{actual.status} error:\n#{actual.body}"
|
113
|
+
when Array
|
114
|
+
"expected that #{actual} fails with one of #{valid_codes}, but got #{actual.status} error:\n#{actual.body}"
|
115
|
+
else
|
116
|
+
"expected that #{actual} fails, but got #{actual.status} error:\n#{actual.body}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
description do
|
121
|
+
'respond with 200 or 201 status code'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Spork::Forker
|
127
|
+
alias_method :_initialize, :initialize
|
128
|
+
def initialize(*args, &block)
|
129
|
+
Grape::Reload::Watcher.reload!
|
130
|
+
_initialize(*args, &block)
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Uploaders
|
2
|
+
class Avatar < CarrierWave::Uploader::Base
|
3
|
+
include CarrierWave::MiniMagick
|
4
|
+
# include CarrierWave::MiniMagick
|
5
|
+
|
6
|
+
# Choose what kind of storage to use for this uploader:
|
7
|
+
storage :file
|
8
|
+
# storage :fog
|
9
|
+
|
10
|
+
# Override the directory where uploaded files will be stored.
|
11
|
+
# This is a sensible default for uploaders that are meant to be mounted:
|
12
|
+
def store_dir
|
13
|
+
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Provide a default URL as a default if there hasn't been a file uploaded:
|
17
|
+
|
18
|
+
# Create different versions of your uploaded files:
|
19
|
+
version :thumb do
|
20
|
+
process :resize_to_fit => [50, 50]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|