api-blocks 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d75318555853ecc826f701851966802312197cdd96df8db360727ea962c27dc0
4
- data.tar.gz: aeab6992d7a5849d6c8cd95f19217a2ca26a7f17342d9b4953d0790ace882707
3
+ metadata.gz: c1886cf50c90d96f98f8d0d3157f4a1ef8e0b75fb4654d7954ed3efbd879113a
4
+ data.tar.gz: 5d108444c19bd159fd1fd5ec6347076e9dc76f25cdeecb269582d0d535ca7ba3
5
5
  SHA512:
6
- metadata.gz: 283db07ca95c270badcffe63a92861dbc2f4adac6eb7fcf4115ef2285ce2dbb945637292dfdfab71a5906a8b39913939f24813ee088cb8941066f855cffd5f2a
7
- data.tar.gz: c6d8718960fde579ab983e6e69c2aab7fa6e3a7c364be1c89e6caa1f058b70b7e1ee099eae34dfceabf2c96e942f0b09b100dec8697e0abdd2b26791fccc5848
6
+ metadata.gz: e7431dc25dcf77ecd1d9595114eaec72cf11e549dcdf799cbd8f62dd4b410fceb6395ea2043bac7a58c5ac691ca9ef66178e07bb7277a7dabea4a5567b4c4046
7
+ data.tar.gz: 81e3a7fa03e212da25370561739a5749450dcc80e3a95d6c81240b586fab4d5cb61fab0baeb6942b0e118561cd57c19e2236cb412a4c58f5016c53f430e2cd63
@@ -1,86 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'join_keys'
4
+
3
5
  # Monkey-patch Blueprinter::AssociationExtractor to use `batch-loader` gem in
4
6
  # order to avoid n+1 queries when serializing associations.
5
7
  #
6
8
  # This does not support associations defined using a `proc` as
7
9
  # `options[:blueprint]`
8
10
  #
9
- class Blueprinter::AssociationExtractor < Blueprinter::Extractor
10
- alias_method :original_extract, :extract
11
+ module Blueprinter
12
+ class AssociationExtractor < Blueprinter::Extractor
13
+ alias original_extract extract
11
14
 
12
- def extract(association_name, object, local_options, options = {})
13
- if !options.fetch(:batch, true)
14
- return original_extract(association_name, object, local_options, options)
15
- end
15
+ def extract(association_name, object, local_options, options = {})
16
+ return original_extract(association_name, object, local_options, options) unless options.fetch(:batch, true)
16
17
 
17
- association = object.association(association_name)
18
+ association = object.association(association_name)
18
19
 
19
- if association.is_a?(ActiveRecord::Associations::HasManyThroughAssociation)
20
- return original_extract(association_name, object, local_options, options)
21
- end
20
+ if association.is_a?(ActiveRecord::Associations::HasManyThroughAssociation)
21
+ return original_extract(association_name, object, local_options, options)
22
+ end
22
23
 
23
- if options[:blueprint].is_a?(Proc)
24
- raise "Cannot load blueprints with a `proc` blueprint option with batch-loader"
25
- end
24
+ raise 'Cannot load blueprints with a `proc` blueprint option with batch-loader' if options[:blueprint].is_a?(Proc)
26
25
 
27
- join_key = association.reflection.join_keys
28
- association_id = object.send(join_key.foreign_key)
29
- association_klass = association.reflection.class_name
26
+ join_key = ::ApiBlocks::Blueprinter::JoinKeys.join_keys(
27
+ association.reflection
28
+ )
30
29
 
31
- default_value = case association
32
- when ActiveRecord::Associations::HasManyAssociation
33
- []
34
- end
30
+ association_id = object.send(join_key.foreign_key)
31
+ association_klass = association.reflection.class_name
35
32
 
36
- view = options[:view] || :default
37
- scope = if options[:block].present?
38
- options[:block].call(object, local_options)
39
- else
40
- {}
41
- end
33
+ default_value = case association
34
+ when ActiveRecord::Associations::HasManyAssociation
35
+ []
36
+ end
37
+
38
+ view = options[:view] || :default
39
+ scope = if options[:block].present?
40
+ options[:block].call(object, local_options)
41
+ else
42
+ {}
43
+ end
42
44
 
43
- BatchLoader.for(association_id).batch(
44
- default_value: default_value,
45
- key: [association_name, association_klass, view, options[:blueprint], scope],
46
- ) do |ids, loader, args|
47
- model = association_klass.safe_constantize
48
- scope = args[:key].last
45
+ BatchLoader.for(association_id).batch(
46
+ default_value: default_value,
47
+ key: [association_name, association_klass, view, options[:blueprint], scope]
48
+ ) do |ids, loader, args|
49
+ model = association_klass.safe_constantize
50
+ scope = args[:key].last
49
51
 
50
- case association
51
- when ActiveRecord::Associations::HasManyAssociation
52
- model.where(join_key.key => ids).merge(scope).each do |record|
53
- loader.call(record.send(join_key.key)) do |memo|
54
- memo << render_blueprint(record, local_options, options)
52
+ case association
53
+ when ActiveRecord::Associations::HasManyAssociation
54
+ model.where(join_key.key => ids).merge(scope).each do |record|
55
+ loader.call(record.send(join_key.key)) do |memo|
56
+ memo << render_blueprint(record, local_options, options)
57
+ end
55
58
  end
59
+ when ActiveRecord::Associations::HasOneAssociation
60
+ model.where(join_key.key => ids).merge(scope).each do |record|
61
+ loader.call(
62
+ record.send(join_key.key),
63
+ render_blueprint(record, local_options, options)
64
+ )
65
+ end
66
+ when ActiveRecord::Associations::BelongsToAssociation
67
+ model.where(join_key.key => ids).merge(scope).each do |record|
68
+ loader.call(
69
+ record.id,
70
+ render_blueprint(record, local_options, options)
71
+ )
72
+ end
73
+ else
74
+ raise "unsupported association kind #{association.class.name}"
56
75
  end
57
- when ActiveRecord::Associations::HasOneAssociation
58
- model.where(join_key.key => ids).merge(scope).each do |record|
59
- loader.call(
60
- record.send(join_key.key),
61
- render_blueprint(record, local_options, options)
62
- )
63
- end
64
- when ActiveRecord::Associations::BelongsToAssociation
65
- model.where(join_key.key => ids).merge(scope).each do |record|
66
- loader.call(
67
- record.id,
68
- render_blueprint(record, local_options, options)
69
- )
70
- end
71
- else
72
- raise "unsupported association kind #{association.class.name}"
73
76
  end
74
77
  end
75
- end
76
78
 
77
- private
79
+ private
78
80
 
79
- def render_blueprint(value, local_options, options = {})
80
- return default_value(options) if value.nil?
81
+ def render_blueprint(value, local_options, options = {})
82
+ return default_value(options) if value.nil?
81
83
 
82
- view = options[:view] || :default
83
- blueprint = association_blueprint(options[:blueprint], value)
84
- blueprint.prepare(value, view_name: view, local_options: local_options)
84
+ view = options[:view] || :default
85
+ blueprint = association_blueprint(options[:blueprint], value)
86
+ blueprint.prepare(value, view_name: view, local_options: local_options)
87
+ end
85
88
  end
86
89
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/join_keys.rb
4
+
5
+ module ApiBlocks
6
+ module Blueprinter
7
+ class JoinKeys
8
+ # Based on https://github.com/MaxLap/activerecord_where_assoc/blob/100318de80dea5f3c177526c3f824fda307ebc04/lib/active_record_where_assoc/active_record_compat.rb
9
+ if ActiveRecord.gem_version >= Gem::Version.new('6.1.0.rc1')
10
+ JoinKeys = Struct.new(:key, :foreign_key)
11
+ def self.join_keys(reflection)
12
+ JoinKeys.new(reflection.join_primary_key, reflection.join_foreign_key)
13
+ end
14
+
15
+ elsif ActiveRecord.gem_version >= Gem::Version.new('5.1')
16
+ def self.join_keys(reflection)
17
+ reflection.join_keys
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # frozen_string_litreal: true
2
4
 
3
5
  require 'pundit'
@@ -17,74 +19,75 @@ require 'active_support/core_ext/module'
17
19
  # pundit_scope :api, :v1
18
20
  # end
19
21
  #
20
- module ApiBlocks::Controller
21
- extend ActiveSupport::Concern
22
-
23
- included do
24
- self.responder = ApiBlocks::Responder
22
+ module ApiBlocks
23
+ module Controller
24
+ extend ActiveSupport::Concern
25
25
 
26
- before_action :verify_request_format!
26
+ included do
27
+ self.responder = ApiBlocks::Responder
27
28
 
28
- include Pundit
29
- rescue_from Pundit::NotAuthorizedError, with: :render_forbidden_error
29
+ before_action :verify_request_format!
30
30
 
31
- # Enable pundit after_action hooks to ensure policies are consistently
32
- # used.
33
- after_action :verify_authorized
34
- after_action :verify_policy_scoped, except: :create
31
+ include Pundit
32
+ rescue_from Pundit::NotAuthorizedError, with: :render_forbidden_error
35
33
 
36
- # Override policy_scope to lookup pundit policies under the `scope`
37
- # namespace
38
- def policy_scope(scope, policy_scope_class: nil)
39
- api_scope = self.class.inherited_pundit_api_scope || []
40
-
41
- super(api_scope + [scope], policy_scope_class: policy_scope_class)
42
- end
34
+ # Enable pundit after_action hooks to ensure policies are consistently
35
+ # used.
36
+ after_action :verify_authorized
37
+ after_action :verify_policy_scoped, except: :create
43
38
 
44
- # Override authorize to lookup pundit policies under the `scope`
45
- # namespace
46
- def authorize(record, query = nil, policy_class: nil)
47
- api_scope = self.class.inherited_pundit_api_scope || []
39
+ # Override policy_scope to lookup pundit policies under the `scope`
40
+ # namespace
41
+ def policy_scope(scope, policy_scope_class: nil)
42
+ api_scope = self.class.inherited_pundit_api_scope || []
48
43
 
49
- super(api_scope + [record], query, policy_class: policy_class)
50
- end
44
+ super(api_scope + [scope], policy_scope_class: policy_scope_class)
45
+ end
51
46
 
52
- handle_api_error Pundit::NotAuthorizedError do |error|
53
- [{ detail: error.message }, :forbidden]
54
- end
55
- end
47
+ # Override authorize to lookup pundit policies under the `scope`
48
+ # namespace
49
+ def authorize(record, query = nil, policy_class: nil)
50
+ api_scope = self.class.inherited_pundit_api_scope || []
56
51
 
57
- class_methods do
58
- # Returns the `pundit_api_scope` value that was defined last looking up into
59
- # the inheritance chain of the current class.
60
- def inherited_pundit_api_scope
61
- ancestors
62
- .select { |a| a.respond_to?(:pundit_api_scope) }
63
- .find(&:pundit_api_scope)
64
- .pundit_api_scope
65
- end
52
+ super(api_scope + [record], query, policy_class: policy_class)
53
+ end
66
54
 
67
- # Provide a default scope to pundit's `PolicyFinder`.
68
- def pundit_scope(*scope)
69
- @pundit_api_scope ||= scope
55
+ handle_api_error Pundit::NotAuthorizedError do |error|
56
+ [{ detail: error.message }, :forbidden]
57
+ end
70
58
  end
71
59
 
72
- def pundit_api_scope
73
- @pundit_api_scope
74
- end
60
+ class_methods do
61
+ # Returns the `pundit_api_scope` value that was defined last looking up into
62
+ # the inheritance chain of the current class.
63
+ def inherited_pundit_api_scope
64
+ ancestors
65
+ .select { |a| a.respond_to?(:pundit_api_scope) }
66
+ .find(&:pundit_api_scope)
67
+ .pundit_api_scope
68
+ end
75
69
 
70
+ # Provide a default scope to pundit's `PolicyFinder`.
71
+ def pundit_scope(*scope)
72
+ @pundit_api_scope ||= scope
73
+ end
76
74
 
77
- # Defines a error handler that returns
78
- def handle_api_error(error_class)
79
- rescue_from error_class do |ex|
80
- problem, status =
81
- if block_given?
82
- yield ex
83
- else
84
- [{ detail: ex.message }, :ok]
85
- end
75
+ def pundit_api_scope
76
+ @pundit_api_scope
77
+ end
86
78
 
87
- render problem: problem, status: status
79
+ # Defines a error handler that returns
80
+ def handle_api_error(error_class)
81
+ rescue_from error_class do |ex|
82
+ problem, status =
83
+ if block_given?
84
+ yield ex
85
+ else
86
+ [{ detail: ex.message }, :ok]
87
+ end
88
+
89
+ render problem: problem, status: status
90
+ end
88
91
  end
89
92
  end
90
93
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # ApiBlocks::Doorkeeper implements API extensions for doorkeeper.
4
- module ApiBlocks::Doorkeeper
5
- extend ActiveSupport::Autoload
4
+ module ApiBlocks
5
+ module Doorkeeper
6
+ extend ActiveSupport::Autoload
6
7
 
7
- autoload :Passwords
8
- autoload :Invitations
8
+ autoload :Passwords
9
+ autoload :Invitations
10
+ end
9
11
  end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # ApiBlocks::Doorkeeper::Invitations implements an API invitation workflow.
4
- module ApiBlocks::Doorkeeper::Invitations
5
- extend ActiveSupport::Autoload
4
+ module ApiBlocks
5
+ module Doorkeeper
6
+ module Invitations
7
+ extend ActiveSupport::Autoload
6
8
 
7
- autoload :Controller
8
- autoload :Application
9
+ autoload :Controller
10
+ autoload :Application
11
+ end
12
+ end
9
13
  end
@@ -8,10 +8,16 @@
8
8
  #
9
9
  # @private
10
10
  #
11
- module ApiBlocks::Doorkeeper::Invitations::Application
12
- extend ActiveSupport::Concern
11
+ module ApiBlocks
12
+ module Doorkeeper
13
+ module Invitations
14
+ module Application
15
+ extend ActiveSupport::Concern
13
16
 
14
- included do
15
- validates :invitation_uri, "doorkeeper/redirect_uri": true
17
+ included do
18
+ validates :invitation_uri, "doorkeeper/redirect_uri": true
19
+ end
20
+ end
21
+ end
16
22
  end
17
23
  end
@@ -2,101 +2,106 @@
2
2
 
3
3
  # ApiBlocks::Doorkeeper::Invitations::Controller implements a devise invitable
4
4
  # API controller.
5
- module ApiBlocks::Doorkeeper::Invitations::Controller
6
- extend ActiveSupport::Concern
7
-
8
- included do # rubocop:disable Metrics/BlockLength
9
- skip_after_action :verify_authorized
10
- skip_after_action :verify_policy_scoped
11
-
12
- # Initialize a new invitation.
13
- def create
14
- user = user_model.invite!(
15
- create_params, current_user, application: oauth_application,
16
- )
17
-
18
- return render(status: :no_content) if user.errors.empty?
19
-
20
- respond_with(user)
21
- end
22
-
23
- # Renders informations about the invited user.
24
- def show
25
- user = user_model.find_by_invitation_token(params[:invitation_token], false)
26
-
27
- if user.nil? || !user.persisted?
28
- return render(
29
- problem: { details: "invalid invitation token" },
30
- status: :bad_request
31
- )
5
+ module ApiBlocks
6
+ module Doorkeeper
7
+ module Invitations
8
+ module Controller
9
+ extend ActiveSupport::Concern
10
+
11
+ included do # rubocop:disable Metrics/BlockLength
12
+ skip_after_action :verify_authorized
13
+ skip_after_action :verify_policy_scoped
14
+
15
+ # Initialize a new invitation.
16
+ def create
17
+ user = user_model.invite!(
18
+ create_params, current_user, application: oauth_application
19
+ )
20
+
21
+ return render(status: :no_content) if user.errors.empty?
22
+
23
+ respond_with(user)
24
+ end
25
+
26
+ # Renders informations about the invited user.
27
+ def show
28
+ user = user_model.find_by_invitation_token(params[:invitation_token], false)
29
+
30
+ if user.nil? || !user.persisted?
31
+ return render(
32
+ problem: { details: 'invalid invitation token' },
33
+ status: :bad_request
34
+ )
35
+ end
36
+
37
+ respond_with(user)
38
+ end
39
+
40
+ # Redirects to the application's redirect uri.
41
+ def callback
42
+ query = {
43
+ invitation_token: params[:invitation_token]
44
+ }.to_query
45
+
46
+ redirect_to("#{oauth_application.invitation_uri}?#{query}")
47
+ end
48
+
49
+ # Finalize the invitation.
50
+ def update
51
+ user = user_model.accept_invitation!(update_params)
52
+
53
+ return respond_with(user) unless user.errors.empty?
54
+
55
+ user.unlock_access! if unlockable?(user)
56
+
57
+ respond_with(Doorkeeper::OAuth::TokenResponse.new(
58
+ access_token(oauth_application, user)
59
+ ).body)
60
+ end
61
+
62
+ private
63
+
64
+ def create_params
65
+ params.require(:user).permit(:email)
66
+ end
67
+
68
+ def update_params
69
+ params.require(:user).permit(
70
+ :invitation_token, :password, :password_confirmation
71
+ )
72
+ end
73
+
74
+ # Copied over from devise base controller in order to determine wether a ser
75
+ # is unlockable or not.
76
+ def unlockable?(resource)
77
+ resource.respond_to?(:unlock_access!) &&
78
+ resource.respond_to?(:unlock_strategy_enabled?) &&
79
+ resource.unlock_strategy_enabled?(:email)
80
+ end
81
+
82
+ # Returns a new access token for this user.
83
+ def access_token(application, user)
84
+ Doorkeeper::AccessToken.find_or_create_for(
85
+ application,
86
+ user.id,
87
+ Doorkeeper.configuration.default_scopes,
88
+ Doorkeeper.configuration.access_token_expires_in,
89
+ true
90
+ )
91
+ end
92
+
93
+ def oauth_application
94
+ @oauth_application ||= Doorkeeper::Application.find_by!(
95
+ uid: params[:client_id]
96
+ )
97
+ end
98
+
99
+ # Returns the user model class.
100
+ def user_model
101
+ raise 'the method `user_model` must be implemented on your invitations controller'
102
+ end
103
+ end
32
104
  end
33
-
34
- respond_with(user)
35
- end
36
-
37
- # Redirects to the application's redirect uri.
38
- def callback
39
- query = {
40
- invitation_token: params[:invitation_token]
41
- }.to_query
42
-
43
- redirect_to("#{oauth_application.invitation_uri}?#{query}")
44
- end
45
-
46
- # Finalize the invitation.
47
- def update
48
- user = user_model.accept_invitation!(update_params)
49
-
50
- return respond_with(user) unless user.errors.empty?
51
-
52
- user.unlock_access! if unlockable?(user)
53
-
54
- respond_with(Doorkeeper::OAuth::TokenResponse.new(
55
- access_token(oauth_application, user)
56
- ).body)
57
- end
58
-
59
- private
60
-
61
- def create_params
62
- params.require(:user).permit(:email)
63
- end
64
-
65
- def update_params
66
- params.require(:user).permit(
67
- :invitation_token, :password, :password_confirmation
68
- )
69
- end
70
-
71
- # Copied over from devise base controller in order to determine wether a ser
72
- # is unlockable or not.
73
- def unlockable?(resource)
74
- resource.respond_to?(:unlock_access!) &&
75
- resource.respond_to?(:unlock_strategy_enabled?) &&
76
- resource.unlock_strategy_enabled?(:email)
77
- end
78
-
79
- # Returns a new access token for this user.
80
- def access_token(application, user)
81
- Doorkeeper::AccessToken.find_or_create_for(
82
- application,
83
- user.id,
84
- Doorkeeper.configuration.default_scopes,
85
- Doorkeeper.configuration.access_token_expires_in,
86
- true
87
- )
88
- end
89
-
90
- def oauth_application
91
- @oauth_application ||= Doorkeeper::Application.find_by!(
92
- uid: params[:client_id]
93
- )
94
- end
95
-
96
-
97
- # Returns the user model class.
98
- def user_model
99
- raise 'the method `user_model` must be implemented on your invitations controller' # rubocop:disable Metrics/LineLength
100
105
  end
101
106
  end
102
107
  end