grape-gen 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|