introspective_grape 0.5.5 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +22 -18
- data/README.md +2 -0
- data/introspective_grape.gemspec +5 -4
- data/lib/introspective_grape/api.rb +461 -454
- data/lib/introspective_grape/create_helpers.rb +25 -25
- data/lib/introspective_grape/traversal.rb +1 -1
- data/lib/introspective_grape/validators.rb +36 -34
- 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 +21 -158
@@ -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,34 +1,36 @@
|
|
1
|
-
require 'grape/validations'
|
2
|
-
module Grape
|
3
|
-
module Validators
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
raise
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
raise
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
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
|