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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +22 -18
  3. data/README.md +2 -0
  4. data/introspective_grape.gemspec +5 -4
  5. data/lib/introspective_grape/api.rb +461 -459
  6. data/lib/introspective_grape/create_helpers.rb +25 -25
  7. data/lib/introspective_grape/traversal.rb +1 -1
  8. data/lib/introspective_grape/validators.rb +36 -36
  9. data/lib/introspective_grape/version.rb +5 -5
  10. data/spec/dummy/Gemfile +23 -22
  11. data/spec/dummy/app/api/api_helpers.rb +38 -37
  12. data/spec/dummy/app/api/dummy_api.rb +61 -61
  13. data/spec/dummy/app/api/permissions_helper.rb +7 -7
  14. data/spec/dummy/app/helpers/current.rb +3 -0
  15. data/spec/dummy/app/models/chat_message.rb +34 -34
  16. data/spec/dummy/app/models/chat_user.rb +16 -16
  17. data/spec/dummy/app/models/location.rb +26 -26
  18. data/spec/dummy/app/models/role.rb +30 -30
  19. data/spec/dummy/app/models/team.rb +14 -9
  20. data/spec/dummy/app/models/user/chatter.rb +79 -79
  21. data/spec/dummy/bin/bundle +3 -3
  22. data/spec/dummy/bin/rails +4 -4
  23. data/spec/dummy/bin/setup +36 -29
  24. data/spec/dummy/bin/update +31 -0
  25. data/spec/dummy/bin/yarn +11 -0
  26. data/spec/dummy/config/application.rb +30 -37
  27. data/spec/dummy/config/boot.rb +3 -6
  28. data/spec/dummy/config/environment.rb +5 -11
  29. data/spec/dummy/config/environments/development.rb +54 -42
  30. data/spec/dummy/config/environments/production.rb +81 -79
  31. data/spec/dummy/config/environments/test.rb +47 -44
  32. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  33. data/spec/dummy/config/initializers/assets.rb +14 -11
  34. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  35. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -3
  36. data/spec/dummy/config/initializers/inflections.rb +16 -16
  37. data/spec/dummy/config/initializers/new_framework_defaults_5_2.rb +38 -0
  38. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -14
  39. data/spec/dummy/config/locales/en.yml +33 -23
  40. data/spec/dummy/config/storage.yml +34 -0
  41. data/spec/models/image_spec.rb +10 -14
  42. data/spec/requests/project_api_spec.rb +185 -182
  43. data/spec/requests/user_api_spec.rb +221 -221
  44. data/spec/support/request_helpers.rb +22 -21
  45. 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.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,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, 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, 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, params: [@scope.full_name(field)], message: 'must be a valid JSON hash!'
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.7'
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