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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +22 -18
  4. data/README.md +2 -0
  5. data/introspective_grape.gemspec +5 -4
  6. data/lib/introspective_grape/api.rb +461 -454
  7. data/lib/introspective_grape/create_helpers.rb +25 -25
  8. data/lib/introspective_grape/traversal.rb +1 -1
  9. data/lib/introspective_grape/validators.rb +36 -34
  10. data/lib/introspective_grape/version.rb +5 -5
  11. data/spec/dummy/Gemfile +23 -22
  12. data/spec/dummy/app/api/api_helpers.rb +38 -37
  13. data/spec/dummy/app/api/dummy_api.rb +61 -61
  14. data/spec/dummy/app/api/permissions_helper.rb +7 -7
  15. data/spec/dummy/app/helpers/current.rb +3 -0
  16. data/spec/dummy/app/models/chat_message.rb +34 -34
  17. data/spec/dummy/app/models/chat_user.rb +16 -16
  18. data/spec/dummy/app/models/location.rb +26 -26
  19. data/spec/dummy/app/models/role.rb +30 -30
  20. data/spec/dummy/app/models/team.rb +14 -9
  21. data/spec/dummy/app/models/user/chatter.rb +79 -79
  22. data/spec/dummy/bin/bundle +3 -3
  23. data/spec/dummy/bin/rails +4 -4
  24. data/spec/dummy/bin/setup +36 -29
  25. data/spec/dummy/bin/update +31 -0
  26. data/spec/dummy/bin/yarn +11 -0
  27. data/spec/dummy/config/application.rb +30 -37
  28. data/spec/dummy/config/boot.rb +3 -6
  29. data/spec/dummy/config/environment.rb +5 -11
  30. data/spec/dummy/config/environments/development.rb +54 -42
  31. data/spec/dummy/config/environments/production.rb +81 -79
  32. data/spec/dummy/config/environments/test.rb +47 -44
  33. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  34. data/spec/dummy/config/initializers/assets.rb +14 -11
  35. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  36. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -3
  37. data/spec/dummy/config/initializers/inflections.rb +16 -16
  38. data/spec/dummy/config/initializers/new_framework_defaults_5_2.rb +38 -0
  39. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
  40. data/spec/dummy/config/locales/en.yml +33 -23
  41. data/spec/dummy/config/storage.yml +34 -0
  42. data/spec/models/image_spec.rb +10 -14
  43. data/spec/requests/project_api_spec.rb +185 -182
  44. data/spec/requests/user_api_spec.rb +221 -221
  45. data/spec/support/request_helpers.rb +22 -21
  46. 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.update_attributes( 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
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..-1].each do |r|
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
- class Json < Grape::Validations::Base
5
- def validate_param!(field, params)
6
- begin
7
- JSON.parse( params[field] )
8
- rescue StandardError
9
- raise Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be valid JSON!'
10
- end
11
- end
12
- end
13
-
14
- class JsonArray < Grape::Validations::Base
15
- def validate_param!(field, params)
16
- begin
17
- raise unless JSON.parse( params[field] ).is_a? Array
18
- rescue StandardError
19
- raise Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be a valid JSON array!'
20
- end
21
- end
22
- end
23
-
24
- class JsonHash < Grape::Validations::Base
25
- def validate_param!(field, params)
26
- begin
27
- raise unless JSON.parse( params[field] ).is_a? Hash
28
- rescue StandardError
29
- raise Grape::Exceptions::Validation, params: [@scope.full_name(field)], message: 'must be a valid JSON hash!'
30
- end
31
- end
32
- end
33
- end
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.5'
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', '5.2.6.2'
3
-
4
- gem 'byebug'
5
- gem 'camel_snake_keys'
6
-
7
- gem 'devise'
8
- gem 'delayed_paperclip'
9
- gem 'kt-paperclip'
10
-
11
- gem 'grape'
12
- gem 'grape-entity'
13
- gem 'grape-kaminari'
14
- gem 'grape-swagger'
15
- gem 'grape-swagger-entity'
16
- gem 'introspective_grape' #, path: '~/Dropbox/contrib/introspective_grape'
17
-
18
- gem 'paperclip'
19
- gem 'pundit'
20
-
21
- gem 'rack-cors'
22
- gem 'sqlite3'
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 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
- end
7
-
8
- def authenticate!
9
- unauthenticated! unless login_request? || current_user
10
- end
11
-
12
- def login_request?
13
- self.method_name.start_with?('POST') && self.namespace == '/sessions'
14
- end
15
-
16
- # returns an 'unauthenticated' response
17
- def unauthenticated!(error_type = nil)
18
- respond_error!('unauthenticated', error_type, 401)
19
- end
20
-
21
- # returns a error response with given type, message_key and status
22
- def respond_error!(type, message_key, status = 500, other = {})
23
- e = {
24
- type: type,
25
- status: status
26
- }
27
- e['message_key'] = message_key if message_key
28
- e.merge!(other)
29
- error!({ error: e }, status)
30
- end
31
-
32
- private
33
-
34
- def safe_params(params)
35
- ActionController::Parameters.new(params)
36
- end
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 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
+ 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
@@ -0,0 +1,3 @@
1
+ module Current
2
+ thread_mattr_accessor :user
3
+ 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[: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
+ 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[: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
+ 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
- end
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