introspective_grape 0.5.7 → 0.6.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 +4 -4
- data/Gemfile +22 -18
- data/README.md +2 -0
- data/introspective_grape.gemspec +5 -4
- data/lib/introspective_grape/api.rb +461 -459
- data/lib/introspective_grape/create_helpers.rb +25 -25
- data/lib/introspective_grape/traversal.rb +1 -1
- data/lib/introspective_grape/validators.rb +36 -36
- data/lib/introspective_grape/version.rb +5 -5
- data/spec/dummy/Gemfile +23 -22
- data/spec/dummy/app/api/api_helpers.rb +38 -37
- data/spec/dummy/app/api/dummy_api.rb +61 -61
- data/spec/dummy/app/api/permissions_helper.rb +7 -7
- data/spec/dummy/app/helpers/current.rb +3 -0
- data/spec/dummy/app/models/chat_message.rb +34 -34
- data/spec/dummy/app/models/chat_user.rb +16 -16
- data/spec/dummy/app/models/location.rb +26 -26
- data/spec/dummy/app/models/role.rb +30 -30
- data/spec/dummy/app/models/team.rb +14 -9
- data/spec/dummy/app/models/user/chatter.rb +79 -79
- data/spec/dummy/bin/bundle +3 -3
- data/spec/dummy/bin/rails +4 -4
- data/spec/dummy/bin/setup +36 -29
- data/spec/dummy/bin/update +31 -0
- data/spec/dummy/bin/yarn +11 -0
- data/spec/dummy/config/application.rb +30 -37
- data/spec/dummy/config/boot.rb +3 -6
- data/spec/dummy/config/environment.rb +5 -11
- data/spec/dummy/config/environments/development.rb +54 -42
- data/spec/dummy/config/environments/production.rb +81 -79
- data/spec/dummy/config/environments/test.rb +47 -44
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +14 -11
- data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -3
- data/spec/dummy/config/initializers/inflections.rb +16 -16
- data/spec/dummy/config/initializers/new_framework_defaults_5_2.rb +38 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
- data/spec/dummy/config/locales/en.yml +33 -23
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/models/image_spec.rb +10 -14
- data/spec/requests/project_api_spec.rb +185 -182
- data/spec/requests/user_api_spec.rb +221 -221
- data/spec/support/request_helpers.rb +22 -21
- metadata +20 -157
@@ -1,25 +1,25 @@
|
|
1
|
-
module IntrospectiveGrape
|
2
|
-
module CreateHelpers
|
3
|
-
def add_new_records_to_root_record(dsl, routes, params, model)
|
4
|
-
dsl.send(:authorize, model, :create?)
|
5
|
-
ActiveRecord::Base.transaction do
|
6
|
-
old = find_leaves(routes, model, params)
|
7
|
-
model.
|
8
|
-
new = find_leaves(routes, model, params)
|
9
|
-
old.respond_to?(:size) ? new - old : new
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def create_new_record(dsl, routes, params)
|
14
|
-
model = routes.first.model.new( dsl.send(:safe_params, params).permit(whitelist) )
|
15
|
-
dsl.send(:authorize, model, :create?)
|
16
|
-
model.save!
|
17
|
-
|
18
|
-
# reload the model with eager loading
|
19
|
-
default_includes = routes.first.klass.default_includes(routes.first.model)
|
20
|
-
model = model.class.includes(default_includes).find(model.id) if model.persisted?
|
21
|
-
|
22
|
-
find_leaves(routes, model, params)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
1
|
+
module IntrospectiveGrape
|
2
|
+
module CreateHelpers
|
3
|
+
def add_new_records_to_root_record(dsl, routes, params, model)
|
4
|
+
dsl.send(:authorize, model, :create?)
|
5
|
+
ActiveRecord::Base.transaction do
|
6
|
+
old = find_leaves(routes, model, params)
|
7
|
+
model.update( dsl.send(:safe_params, params).permit(whitelist) )
|
8
|
+
new = find_leaves(routes, model, params)
|
9
|
+
old.respond_to?(:size) ? new - old : new
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_new_record(dsl, routes, params)
|
14
|
+
model = routes.first.model.new( dsl.send(:safe_params, params).permit(whitelist) )
|
15
|
+
dsl.send(:authorize, model, :create?)
|
16
|
+
model.save!
|
17
|
+
|
18
|
+
# reload the model with eager loading
|
19
|
+
default_includes = routes.first.klass.default_includes(routes.first.model)
|
20
|
+
model = model.class.includes(default_includes).find(model.id) if model.persisted?
|
21
|
+
|
22
|
+
find_leaves(routes, model, params)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -36,7 +36,7 @@ module IntrospectiveGrape
|
|
36
36
|
# For deeply nested routes we need to search from the root of the API to the leaf
|
37
37
|
# of its nested associations in order to guarantee the validity of the relationship,
|
38
38
|
# the authorization on the parent model, and the sanity of passed parameters.
|
39
|
-
routes[1
|
39
|
+
routes[1..].each do |r|
|
40
40
|
if record && params[r.key]
|
41
41
|
ref = r.reflection
|
42
42
|
record = record.send(ref.name).where( id: params[r.key] ).first if ref
|
@@ -1,36 +1,36 @@
|
|
1
|
-
require 'grape/validations'
|
2
|
-
module Grape
|
3
|
-
module Validators
|
4
|
-
# Validations::Base becomes Validators::Base somewhere between 1.6.0 and 1.6.2
|
5
|
-
validation_class = defined?(Grape::Validations::Base) ? Grape::Validations::Base : Grape::Validations::Validators::Base
|
6
|
-
class Json < validation_class
|
7
|
-
def validate_param!(field, params)
|
8
|
-
begin
|
9
|
-
JSON.parse( params[field] )
|
10
|
-
rescue StandardError
|
11
|
-
raise Grape::Exceptions::Validation
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class JsonArray < validation_class
|
17
|
-
def validate_param!(field, params)
|
18
|
-
begin
|
19
|
-
raise unless JSON.parse( params[field] ).is_a? Array
|
20
|
-
rescue StandardError
|
21
|
-
raise Grape::Exceptions::Validation
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class JsonHash < validation_class
|
27
|
-
def validate_param!(field, params)
|
28
|
-
begin
|
29
|
-
raise unless JSON.parse( params[field] ).is_a? Hash
|
30
|
-
rescue StandardError
|
31
|
-
raise Grape::Exceptions::Validation
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
1
|
+
require 'grape/validations'
|
2
|
+
module Grape
|
3
|
+
module Validators
|
4
|
+
# Validations::Base becomes Validators::Base somewhere between 1.6.0 and 1.6.2
|
5
|
+
validation_class = defined?(Grape::Validations::Base) ? Grape::Validations::Base : Grape::Validations::Validators::Base
|
6
|
+
class Json < validation_class
|
7
|
+
def validate_param!(field, params)
|
8
|
+
begin
|
9
|
+
JSON.parse( params[field] )
|
10
|
+
rescue StandardError
|
11
|
+
raise Grape::Exceptions::Validation.new params: [@scope.full_name(field)], message: 'must be valid JSON!'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class JsonArray < validation_class
|
17
|
+
def validate_param!(field, params)
|
18
|
+
begin
|
19
|
+
raise unless JSON.parse( params[field] ).is_a? Array
|
20
|
+
rescue StandardError
|
21
|
+
raise Grape::Exceptions::Validation.new params: [@scope.full_name(field)], message: 'must be a valid JSON array!'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class JsonHash < validation_class
|
27
|
+
def validate_param!(field, params)
|
28
|
+
begin
|
29
|
+
raise unless JSON.parse( params[field] ).is_a? Hash
|
30
|
+
rescue StandardError
|
31
|
+
raise Grape::Exceptions::Validation.new params: [@scope.full_name(field)], message: 'must be a valid JSON hash!'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module IntrospectiveGrape
|
4
|
-
VERSION = '0.
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IntrospectiveGrape
|
4
|
+
VERSION = '0.6.1'
|
5
|
+
end
|
data/spec/dummy/Gemfile
CHANGED
@@ -1,22 +1,23 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
gem 'rails'
|
3
|
-
|
4
|
-
gem 'byebug'
|
5
|
-
gem '
|
6
|
-
|
7
|
-
|
8
|
-
gem '
|
9
|
-
gem '
|
10
|
-
|
11
|
-
|
12
|
-
gem 'grape
|
13
|
-
gem 'grape-
|
14
|
-
gem 'grape-
|
15
|
-
gem 'grape-swagger
|
16
|
-
gem '
|
17
|
-
|
18
|
-
|
19
|
-
gem '
|
20
|
-
|
21
|
-
|
22
|
-
gem '
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
gem 'rails' #, '5.2.6.2'
|
3
|
+
|
4
|
+
gem 'byebug'
|
5
|
+
gem 'listen'
|
6
|
+
gem 'camel_snake_keys'
|
7
|
+
|
8
|
+
gem 'devise'
|
9
|
+
gem 'delayed_paperclip'
|
10
|
+
gem 'kt-paperclip'
|
11
|
+
|
12
|
+
gem 'grape'
|
13
|
+
gem 'grape-entity'
|
14
|
+
gem 'grape-kaminari'
|
15
|
+
gem 'grape-swagger'
|
16
|
+
gem 'grape-swagger-entity'
|
17
|
+
gem 'introspective_grape', path: '~/Dropbox/contrib/introspective_grape'
|
18
|
+
|
19
|
+
gem 'paperclip'
|
20
|
+
gem 'pundit'
|
21
|
+
|
22
|
+
gem 'rack-cors'
|
23
|
+
gem 'sqlite3'
|
@@ -1,37 +1,38 @@
|
|
1
|
-
module
|
2
|
-
def current_user
|
3
|
-
params[:api_key].present? && @user = User.find_by_authentication_token(params[:api_key])
|
4
|
-
# for testing in situ
|
5
|
-
@user = User.find_or_create_by(email: 'test@test.com', superuser: true, authentication_token: '1234567890', first_name: "First", last_name: "Last")
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
e
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
1
|
+
module APIHelpers
|
2
|
+
def current_user
|
3
|
+
params[:api_key].present? && @user = User.find_by_authentication_token(params[:api_key])
|
4
|
+
# for testing in situ
|
5
|
+
@user = User.find_or_create_by(email: 'test@test.com', superuser: true, authentication_token: '1234567890', first_name: "First", last_name: "Last")
|
6
|
+
Current.user = @user
|
7
|
+
end
|
8
|
+
|
9
|
+
def authenticate!
|
10
|
+
unauthenticated! unless login_request? || current_user
|
11
|
+
end
|
12
|
+
|
13
|
+
def login_request?
|
14
|
+
self.method_name.start_with?('POST') && self.namespace == '/sessions'
|
15
|
+
end
|
16
|
+
|
17
|
+
# returns an 'unauthenticated' response
|
18
|
+
def unauthenticated!(error_type = nil)
|
19
|
+
respond_error!('unauthenticated', error_type, 401)
|
20
|
+
end
|
21
|
+
|
22
|
+
# returns a error response with given type, message_key and status
|
23
|
+
def respond_error!(type, message_key, status = 500, other = {})
|
24
|
+
e = {
|
25
|
+
type: type,
|
26
|
+
status: status
|
27
|
+
}
|
28
|
+
e['message_key'] = message_key if message_key
|
29
|
+
e.merge!(other)
|
30
|
+
error!({ error: e }, status)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def safe_params(params)
|
36
|
+
ActionController::Parameters.new(params)
|
37
|
+
end
|
38
|
+
end
|
@@ -1,61 +1,61 @@
|
|
1
|
-
require 'byebug'
|
2
|
-
require 'grape-kaminari'
|
3
|
-
class DummyAPI < Grape::API #::Instance
|
4
|
-
include Grape::Kaminari
|
5
|
-
|
6
|
-
version 'v1', using: :path
|
7
|
-
format :json
|
8
|
-
formatter :json, IntrospectiveGrape::Formatter::CamelJson
|
9
|
-
default_format :json
|
10
|
-
|
11
|
-
|
12
|
-
include ErrorHandlers
|
13
|
-
helpers PermissionsHelper
|
14
|
-
helpers
|
15
|
-
|
16
|
-
USER_NOT_CONFIRMED = 'user_not_confirmed'.freeze
|
17
|
-
BAD_LOGIN = 'bad_login'.freeze
|
18
|
-
|
19
|
-
before do
|
20
|
-
# sets server date in response header. This can be used on the client side
|
21
|
-
header "X-Server-Date", Time.now.to_i.to_s
|
22
|
-
header "Expires", 1.year.ago.httpdate
|
23
|
-
end
|
24
|
-
|
25
|
-
before_validation do
|
26
|
-
Rails.logger.info "With params: #{params.to_hash.inspect}"
|
27
|
-
end
|
28
|
-
|
29
|
-
after do
|
30
|
-
unless self.options[:path].first =~ /swagger/
|
31
|
-
verify_authorized # Ensure that all endpoints are authorized by a policy class
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Load the in-memory database for the test app
|
36
|
-
load "#{Rails.root}/db/schema.rb"
|
37
|
-
|
38
|
-
# Mount every api endpoint under app/api/dummy/.
|
39
|
-
Dir.glob(Rails.root+"app"+"api"+'dummy'+'*.rb').each do |f|
|
40
|
-
api = "Dummy::#{File.basename(f, '.rb').camelize.sub(/Api$/,'API')}"
|
41
|
-
api = api.constantize
|
42
|
-
mount api if api.respond_to? :endpoints
|
43
|
-
end
|
44
|
-
|
45
|
-
# configure grape-swagger to auto-generate swagger docs
|
46
|
-
add_swagger_documentation({
|
47
|
-
base_path: "/api",
|
48
|
-
doc_version: 'v1',
|
49
|
-
hide_documentation_path: true,
|
50
|
-
format: :json,
|
51
|
-
hide_format: true,
|
52
|
-
security_definitions: {
|
53
|
-
api_key: {
|
54
|
-
type: "apiKey",
|
55
|
-
name: "api_key",
|
56
|
-
in: "header"
|
57
|
-
}
|
58
|
-
}
|
59
|
-
})
|
60
|
-
|
61
|
-
end
|
1
|
+
require 'byebug'
|
2
|
+
require 'grape-kaminari'
|
3
|
+
class DummyAPI < Grape::API #::Instance
|
4
|
+
include Grape::Kaminari
|
5
|
+
|
6
|
+
version 'v1', using: :path
|
7
|
+
format :json
|
8
|
+
formatter :json, IntrospectiveGrape::Formatter::CamelJson
|
9
|
+
default_format :json
|
10
|
+
|
11
|
+
|
12
|
+
include ErrorHandlers
|
13
|
+
helpers PermissionsHelper
|
14
|
+
helpers APIHelpers
|
15
|
+
|
16
|
+
USER_NOT_CONFIRMED = 'user_not_confirmed'.freeze
|
17
|
+
BAD_LOGIN = 'bad_login'.freeze
|
18
|
+
|
19
|
+
before do
|
20
|
+
# sets server date in response header. This can be used on the client side
|
21
|
+
header "X-Server-Date", Time.now.to_i.to_s
|
22
|
+
header "Expires", 1.year.ago.httpdate
|
23
|
+
end
|
24
|
+
|
25
|
+
before_validation do
|
26
|
+
Rails.logger.info "With params: #{params.to_hash.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
unless self.options[:path].first =~ /swagger/
|
31
|
+
verify_authorized # Ensure that all endpoints are authorized by a policy class
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Load the in-memory database for the test app
|
36
|
+
load "#{Rails.root}/db/schema.rb"
|
37
|
+
|
38
|
+
# Mount every api endpoint under app/api/dummy/.
|
39
|
+
Dir.glob(Rails.root+"app"+"api"+'dummy'+'*.rb').each do |f|
|
40
|
+
api = "Dummy::#{File.basename(f, '.rb').camelize.sub(/Api$/,'API')}"
|
41
|
+
api = api.constantize
|
42
|
+
mount api if api.respond_to? :endpoints
|
43
|
+
end
|
44
|
+
|
45
|
+
# configure grape-swagger to auto-generate swagger docs
|
46
|
+
add_swagger_documentation({
|
47
|
+
base_path: "/api",
|
48
|
+
doc_version: 'v1',
|
49
|
+
hide_documentation_path: true,
|
50
|
+
format: :json,
|
51
|
+
hide_format: true,
|
52
|
+
security_definitions: {
|
53
|
+
api_key: {
|
54
|
+
type: "apiKey",
|
55
|
+
name: "api_key",
|
56
|
+
in: "header"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
})
|
60
|
+
|
61
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'pundit'
|
2
|
-
module PermissionsHelper
|
3
|
-
# Pundit won't import it's methods unless it sees a stub of ActionController's hide_action.
|
4
|
-
def hide_action; end
|
5
|
-
include Pundit
|
6
|
-
|
7
|
-
end
|
1
|
+
require 'pundit'
|
2
|
+
module PermissionsHelper
|
3
|
+
# Pundit won't import it's methods unless it sees a stub of ActionController's hide_action.
|
4
|
+
def hide_action; end
|
5
|
+
include Pundit::Authorization
|
6
|
+
|
7
|
+
end
|
@@ -1,34 +1,34 @@
|
|
1
|
-
class ChatMessage < AbstractAdapter
|
2
|
-
belongs_to :chat
|
3
|
-
belongs_to :author, class_name: 'User'
|
4
|
-
|
5
|
-
has_many :chat_users, through: :chat
|
6
|
-
has_many :recipients, lambda {|message| where(':created_at >= chat_users.created_at and (chat_users.departed_at IS NULL OR :created_at <= chat_users.departed_at)', created_at: message.created_at ) }, through: :chat_users, source: :user, class_name: 'User'
|
7
|
-
|
8
|
-
# Create ChatUserMessage records for each recipient to track read status
|
9
|
-
has_many :chat_message_users, dependent: :destroy
|
10
|
-
|
11
|
-
validate :author_in_chat
|
12
|
-
|
13
|
-
def author_in_chat
|
14
|
-
errors
|
15
|
-
end
|
16
|
-
|
17
|
-
before_save :create_message_users, if: :new_record?
|
18
|
-
def create_message_users
|
19
|
-
chat_users.merge(ChatUser.current).each do |cu|
|
20
|
-
chat_message_users.build(user: cu.user)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def read_by?(user)
|
25
|
-
chat_message_users.merge(ChatMessageUser.read).map(&:user_id).include?(user.id)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.find_chat_for_users(users)
|
29
|
-
# presumably much more efficient ways to run an intersecton, we want to find the last
|
30
|
-
# exact match with the users being messaged to append to the existing chat.
|
31
|
-
Chat.eager_load(:chat_users).where("chat_users.departed_at IS NULL").order('chats.created_at desc').detect {|c| c.chat_users.map(&:user_id).uniq.sort == users.map(&:id).sort }
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
1
|
+
class ChatMessage < AbstractAdapter
|
2
|
+
belongs_to :chat
|
3
|
+
belongs_to :author, class_name: 'User'
|
4
|
+
|
5
|
+
has_many :chat_users, through: :chat
|
6
|
+
has_many :recipients, lambda {|message| where(':created_at >= chat_users.created_at and (chat_users.departed_at IS NULL OR :created_at <= chat_users.departed_at)', created_at: message.created_at ) }, through: :chat_users, source: :user, class_name: 'User'
|
7
|
+
|
8
|
+
# Create ChatUserMessage records for each recipient to track read status
|
9
|
+
has_many :chat_message_users, dependent: :destroy
|
10
|
+
|
11
|
+
validate :author_in_chat
|
12
|
+
|
13
|
+
def author_in_chat
|
14
|
+
errors.add(:base, 'User not in chat session.') unless chat.active_users.include? author
|
15
|
+
end
|
16
|
+
|
17
|
+
before_save :create_message_users, if: :new_record?
|
18
|
+
def create_message_users
|
19
|
+
chat_users.merge(ChatUser.current).each do |cu|
|
20
|
+
chat_message_users.build(user: cu.user)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_by?(user)
|
25
|
+
chat_message_users.merge(ChatMessageUser.read).map(&:user_id).include?(user.id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.find_chat_for_users(users)
|
29
|
+
# presumably much more efficient ways to run an intersecton, we want to find the last
|
30
|
+
# exact match with the users being messaged to append to the existing chat.
|
31
|
+
Chat.eager_load(:chat_users).where("chat_users.departed_at IS NULL").order('chats.created_at desc').detect {|c| c.chat_users.map(&:user_id).uniq.sort == users.map(&:id).sort }
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -1,16 +1,16 @@
|
|
1
|
-
class ChatUser < AbstractAdapter
|
2
|
-
belongs_to :chat
|
3
|
-
belongs_to :user
|
4
|
-
|
5
|
-
alias_attribute :joined_at, :created_at
|
6
|
-
alias_attribute :left_at, :departed_at
|
7
|
-
|
8
|
-
scope :current, ->{ where(departed_at: nil) }
|
9
|
-
|
10
|
-
validate :user_not_already_active, on: :create
|
11
|
-
|
12
|
-
def user_not_already_active
|
13
|
-
errors
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
1
|
+
class ChatUser < AbstractAdapter
|
2
|
+
belongs_to :chat
|
3
|
+
belongs_to :user
|
4
|
+
|
5
|
+
alias_attribute :joined_at, :created_at
|
6
|
+
alias_attribute :left_at, :departed_at
|
7
|
+
|
8
|
+
scope :current, ->{ where(departed_at: nil) }
|
9
|
+
|
10
|
+
validate :user_not_already_active, on: :create
|
11
|
+
|
12
|
+
def user_not_already_active
|
13
|
+
errors.add(:base, "#{user.name} is already present in this chat.") if chat.chat_users.where(user_id: user.id, departed_at: nil).count > 0 && user.persisted?
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -1,26 +1,26 @@
|
|
1
|
-
class Location < AbstractAdapter
|
2
|
-
has_many :locatables, dependent: :destroy
|
3
|
-
has_many :companies, through: :locatables, source: :locatable, source_type: 'Company'
|
4
|
-
|
5
|
-
has_many :beacons, class_name: 'LocationBeacon', dependent: :destroy
|
6
|
-
has_one :gps, class_name: 'LocationGps', dependent: :destroy
|
7
|
-
delegate :lat,:lng,:alt, to: :gps
|
8
|
-
|
9
|
-
belongs_to :parent_location, foreign_key: :parent_location_id, class_name: 'Location', inverse_of: :child_locations
|
10
|
-
has_many :child_locations, foreign_key: :parent_location_id, class_name: 'Location', dependent: :destroy, inverse_of: :parent_location
|
11
|
-
|
12
|
-
has_many :user_locations, dependent: :destroy
|
13
|
-
|
14
|
-
# isn't this list going to be kinda long? are there any reasonable constraints to put
|
15
|
-
# on this random bit of metadata?
|
16
|
-
validates_inclusion_of :kind, in: %w(airport terminal gate plane)
|
17
|
-
|
18
|
-
accepts_nested_attributes_for :child_locations, allow_destroy: true
|
19
|
-
accepts_nested_attributes_for :gps, allow_destroy: true
|
20
|
-
accepts_nested_attributes_for :beacons, allow_destroy: true
|
21
|
-
|
22
|
-
def coords
|
23
|
-
[gps.lat, gps.lng, gps.alt]
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
1
|
+
class Location < AbstractAdapter
|
2
|
+
has_many :locatables, dependent: :destroy
|
3
|
+
has_many :companies, through: :locatables, source: :locatable, source_type: 'Company'
|
4
|
+
|
5
|
+
has_many :beacons, class_name: 'LocationBeacon', dependent: :destroy
|
6
|
+
has_one :gps, class_name: 'LocationGps', dependent: :destroy
|
7
|
+
delegate :lat,:lng,:alt, to: :gps
|
8
|
+
|
9
|
+
belongs_to :parent_location, foreign_key: :parent_location_id, class_name: 'Location', inverse_of: :child_locations, optional: true
|
10
|
+
has_many :child_locations, foreign_key: :parent_location_id, class_name: 'Location', dependent: :destroy, inverse_of: :parent_location
|
11
|
+
|
12
|
+
has_many :user_locations, dependent: :destroy
|
13
|
+
|
14
|
+
# isn't this list going to be kinda long? are there any reasonable constraints to put
|
15
|
+
# on this random bit of metadata?
|
16
|
+
validates_inclusion_of :kind, in: %w(airport terminal gate plane)
|
17
|
+
|
18
|
+
accepts_nested_attributes_for :child_locations, allow_destroy: true
|
19
|
+
accepts_nested_attributes_for :gps, allow_destroy: true
|
20
|
+
accepts_nested_attributes_for :beacons, allow_destroy: true
|
21
|
+
|
22
|
+
def coords
|
23
|
+
[gps.lat, gps.lng, gps.alt]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -1,30 +1,30 @@
|
|
1
|
-
class Role < AbstractAdapter
|
2
|
-
belongs_to :user
|
3
|
-
belongs_to :ownable, polymorphic: true
|
4
|
-
|
5
|
-
validates_uniqueness_of :user_id, scope: [:ownable_type,:ownable_id], unless: Proc.new {|u| u.user_id.nil? }, message: "user has already been assigned that role"
|
6
|
-
OWNABLE_TYPES = %w(Company Project).freeze
|
7
|
-
validates_inclusion_of :ownable_type, in: OWNABLE_TYPES
|
8
|
-
|
9
|
-
delegate :email, to: :user, allow_nil: true
|
10
|
-
def attributes
|
11
|
-
super.merge(email: email)
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.grape_validations
|
15
|
-
{ ownable_type: { values: OWNABLE_TYPES } }
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.ownable_assign_options(_model=nil)
|
19
|
-
(Company.all + Project.all).map { |i| [ "#{i.class}: #{i.name}", "#{i.class}-#{i.id}"] }
|
20
|
-
end
|
21
|
-
|
22
|
-
def ownable_assign
|
23
|
-
ownable.present? ? "#{ownable_type}-#{ownable_id}" : nil
|
24
|
-
end
|
25
|
-
|
26
|
-
def ownable_assign=(value)
|
27
|
-
self.ownable_type,self.ownable_id = value.split('-')
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
1
|
+
class Role < AbstractAdapter
|
2
|
+
belongs_to :user
|
3
|
+
belongs_to :ownable, polymorphic: true, optional: true
|
4
|
+
|
5
|
+
validates_uniqueness_of :user_id, scope: [:ownable_type,:ownable_id], unless: Proc.new {|u| u.user_id.nil? }, message: "user has already been assigned that role"
|
6
|
+
OWNABLE_TYPES = %w(Company Project).freeze
|
7
|
+
validates_inclusion_of :ownable_type, in: OWNABLE_TYPES
|
8
|
+
|
9
|
+
delegate :email, to: :user, allow_nil: true
|
10
|
+
def attributes
|
11
|
+
super.merge(email: email)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.grape_validations
|
15
|
+
{ ownable_type: { values: OWNABLE_TYPES } }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.ownable_assign_options(_model=nil)
|
19
|
+
(Company.all + Project.all).map { |i| [ "#{i.class}: #{i.name}", "#{i.class}-#{i.id}"] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def ownable_assign
|
23
|
+
ownable.present? ? "#{ownable_type}-#{ownable_id}" : nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def ownable_assign=(value)
|
27
|
+
self.ownable_type,self.ownable_id = value.split('-')
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -1,9 +1,14 @@
|
|
1
|
-
class Team < AbstractAdapter
|
2
|
-
belongs_to :project
|
3
|
-
belongs_to :creator, class_name: 'User'
|
4
|
-
|
5
|
-
has_many :team_users, inverse_of: :team
|
6
|
-
has_many :users, through: :team_users
|
7
|
-
accepts_nested_attributes_for :team_users, allow_destroy: true
|
8
|
-
|
9
|
-
|
1
|
+
class Team < AbstractAdapter
|
2
|
+
belongs_to :project
|
3
|
+
belongs_to :creator, class_name: 'User'
|
4
|
+
|
5
|
+
has_many :team_users, inverse_of: :team
|
6
|
+
has_many :users, through: :team_users
|
7
|
+
accepts_nested_attributes_for :team_users, allow_destroy: true
|
8
|
+
|
9
|
+
before_validation :set_creator
|
10
|
+
|
11
|
+
def set_creator
|
12
|
+
self.creator ||= Current.user
|
13
|
+
end
|
14
|
+
end
|