diff --git a/.rubocop.yml b/.rubocop.yml index c2b8da4..fbefb72 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,13 +16,11 @@ AllCops: TargetRubyVersion: 2.4 # Include common Ruby source files. Include: - - '**/*.gemspec' - '**/*.podspec' - '**/*.jbuilder' - '**/*.rake' - '**/*.opal' - '**/config.ru' - - '**/Gemfile' - '**/Rakefile' - '**/Capfile' - '**/Guardfile' @@ -35,6 +33,8 @@ AllCops: - '**/Fastfile' - '**/*Fastfile' Exclude: + - '**/*.gemspec' + - '**/Gemfile' - 'bin/bundle' - 'bin/rails' - 'bin/rake' @@ -471,17 +471,6 @@ Style/EmptyMethod: - compact - expanded -# Checks whether the source file has a utf-8 encoding comment or not -# AutoCorrectEncodingComment must match the regex -# /#.*coding\s?[:=]\s?(?:UTF|utf)-8/ -Style/Encoding: - EnforcedStyle: never - SupportedStyles: - - when_needed - - always - - never - AutoCorrectEncodingComment: '# encoding: utf-8' - Layout/EndOfLine: # The `native` style means that CR+LF (Carriage Return + Line Feed) is # enforced on Windows, and LF is enforced on other platforms. The other styles @@ -500,7 +489,7 @@ Layout/ExtraSpacing: # When true, forces the alignment of `=` in assignments on consecutive lines. ForceEqualSignAlignment: false -Style/FileName: +Naming/FileName: # File names listed in `AllCops:Include` are excluded by default. Add extra # excludes here. Exclude: ['lib/graphql-pundit.rb'] @@ -731,7 +720,7 @@ Style/MethodDefParentheses: - require_no_parentheses - require_no_parentheses_except_multiline -Style/MethodName: +Naming/MethodName: EnforcedStyle: snake_case SupportedStyles: - snake_case @@ -902,7 +891,7 @@ Style/PercentQLiterals: - lower_case_q # Use `%q` when possible, `%Q` when necessary - upper_case_q # Always use `%Q` -Style/PredicateName: +Naming/PredicateName: # Predicate name prefixes. NamePrefix: - is_ @@ -1169,13 +1158,13 @@ Style/TrivialAccessors: - to_s - to_sym -Style/VariableName: +Naming/VariableName: EnforcedStyle: snake_case SupportedStyles: - snake_case - camelCase -Style/VariableNumber: +Naming/VariableNumber: EnforcedStyle: normalcase SupportedStyles: - snake_case diff --git a/.rubocop_enabled.yml b/.rubocop_enabled.yml index 1bb8477..560139b 100644 --- a/.rubocop_enabled.yml +++ b/.rubocop_enabled.yml @@ -338,13 +338,59 @@ Layout/TrailingWhitespace: StyleGuide: '#no-trailing-whitespace' Enabled: true -#################### Style ############################### +#################### Naming ############################## -Style/AccessorMethodName: +Naming/AccessorMethodName: Description: Check the naming of accessor methods for get_/set_. StyleGuide: '#accessor_mutator_method_names' Enabled: true +Naming/AsciiIdentifiers: + Description: 'Use only ascii symbols in identifiers.' + StyleGuide: '#english-identifiers' + Enabled: true + +Naming/BinaryOperatorParameterName: + Description: 'When defining binary operators, name the argument other.' + StyleGuide: '#other-arg' + Enabled: true + +Naming/ClassAndModuleCamelCase: + Description: 'Use CamelCase for classes and modules.' + StyleGuide: '#camelcase-classes' + Enabled: true + +Naming/ConstantName: + Description: 'Constants should use SCREAMING_SNAKE_CASE.' + StyleGuide: '#screaming-snake-case' + Enabled: true + +Naming/FileName: + Description: 'Use snake_case for source file names.' + StyleGuide: '#snake-case-files' + Enabled: true + +Naming/MethodName: + Description: 'Use the configured style when naming methods.' + StyleGuide: '#snake-case-symbols-methods-vars' + Enabled: true + +Naming/PredicateName: + Description: 'Check the names of predicate methods.' + StyleGuide: '#bool-methods-qmark' + Enabled: true + +Naming/VariableName: + Description: 'Use the configured style when naming variables.' + StyleGuide: '#snake-case-symbols-methods-vars' + Enabled: true + +Naming/VariableNumber: + Description: 'Use the configured style when numbering variables.' + Enabled: true + +#################### Style ############################### + Style/Alias: Description: 'Use alias instead of alias_method.' StyleGuide: '#alias-method' @@ -365,11 +411,6 @@ Style/AsciiComments: StyleGuide: '#english-comments' Enabled: true -Style/AsciiIdentifiers: - Description: 'Use only ascii symbols in identifiers.' - StyleGuide: '#english-identifiers' - Enabled: true - Style/Attr: Description: 'Checks for uses of Module#attr.' StyleGuide: '#attr' @@ -412,11 +453,6 @@ Style/CharacterLiteral: StyleGuide: '#no-character-literals' Enabled: true -Style/ClassAndModuleCamelCase: - Description: 'Use CamelCase for classes and modules.' - StyleGuide: '#camelcase-classes' - Enabled: true - Style/ClassAndModuleChildren: Description: 'Checks style of children classes and modules.' Enabled: true @@ -459,11 +495,6 @@ Style/ConditionalAssignment: of assigning that variable inside of each branch. Enabled: true -Style/ConstantName: - Description: 'Constants should use SCREAMING_SNAKE_CASE.' - StyleGuide: '#screaming-snake-case' - Enabled: true - Style/DefWithParentheses: Description: 'Use def with parentheses when there are arguments.' StyleGuide: '#method-parens' @@ -519,11 +550,6 @@ Style/EvenOdd: StyleGuide: '#predicate-methods' Enabled: true -Style/FileName: - Description: 'Use snake_case for source file names.' - StyleGuide: '#snake-case-files' - Enabled: true - Style/FrozenStringLiteralComment: Description: >- Add the frozen_string_literal comment to the top of files @@ -634,11 +660,6 @@ Style/MethodDefParentheses: StyleGuide: '#method-parens' Enabled: true -Style/MethodName: - Description: 'Use the configured style when naming methods.' - StyleGuide: '#snake-case-symbols-methods-vars' - Enabled: true - Style/MethodMissing: Description: 'Avoid using `method_missing`.' StyleGuide: '#no-method-missing' @@ -768,11 +789,6 @@ Style/OneLineConditional: StyleGuide: '#ternary-operator' Enabled: true -Style/OpMethod: - Description: 'When defining binary operators, name the argument other.' - StyleGuide: '#other-arg' - Enabled: true - Style/OptionalArguments: Description: >- Checks for optional arguments that do not appear at the end @@ -809,11 +825,6 @@ Style/PerlBackrefs: StyleGuide: '#no-perl-regexp-last-matchers' Enabled: true -Style/PredicateName: - Description: 'Check the names of predicate methods.' - StyleGuide: '#bool-methods-qmark' - Enabled: true - Style/PreferredHashMethods: Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' StyleGuide: '#hash-key' @@ -988,15 +999,6 @@ Style/VariableInterpolation: StyleGuide: '#curlies-interpolate' Enabled: true -Style/VariableName: - Description: 'Use the configured style when naming variables.' - StyleGuide: '#snake-case-symbols-methods-vars' - Enabled: true - -Style/VariableNumber: - Description: 'Use the configured style when numbering variables.' - Enabled: true - Style/WhenThen: Description: 'Use when x then ... for one-line cases.' StyleGuide: '#one-line-cases' @@ -1217,16 +1219,6 @@ Lint/InheritException: Description: 'Avoid inheriting from the `Exception` class.' Enabled: true -Lint/InvalidCharacterLiteral: - Description: >- - Checks for invalid character literals with a non-escaped - whitespace character. - Enabled: true - -Lint/LiteralInCondition: - Description: 'Checks of literals used in conditions.' - Enabled: true - Lint/LiteralInInterpolation: Description: 'Checks for literals used in interpolation.' Enabled: true diff --git a/.travis.yml b/.travis.yml index a89d8ac..a921ea0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,21 @@ -sudo: required +sudo: false dist: trusty language: ruby rvm: - 2.2.7 - - 2.3.4 + - 2.3.5 - 2.4.2 + - 2.5.0 notifications: email: false +matrix: + fast_finish: true + allow_failures: + - rvm: 2.5.0 + script: - bundle exec rspec --format progress diff --git a/README.md b/README.md index aeea1ed..3c2d51c 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ field :createUser end ``` +You can also pass a `lambda` as a record. This receives the usual three arguments (parent value, arguments, context) and returns the value to be used as a record. + You might have also noticed the use of `authorize!` instead of `authorize` in this example. The difference between the two is this: - `authorize` will set the field to `nil` if authorization fails diff --git a/graphql-pundit.gemspec b/graphql-pundit.gemspec index 5167823..d216e09 100644 --- a/graphql-pundit.gemspec +++ b/graphql-pundit.gemspec @@ -30,6 +30,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'codecov', '~> 0.1.10' spec.add_development_dependency 'fuubar', '~> 2.2.0' spec.add_development_dependency 'pry', '~> 0.11.0' + spec.add_development_dependency 'pry-byebug', '~> 3.5.0' + spec.add_development_dependency 'pry-rescue', '~> 1.4.4' + spec.add_development_dependency 'pry-stack_explorer', '~> 0.4.9.2' spec.add_development_dependency 'rake', '~> 12.0' spec.add_development_dependency 'rspec', '~> 3.6' spec.add_development_dependency 'rubocop', '~> 0.51.0' diff --git a/lib/graphql-pundit.rb b/lib/graphql-pundit.rb index 2decd69..fcb36b9 100644 --- a/lib/graphql-pundit.rb +++ b/lib/graphql-pundit.rb @@ -7,7 +7,9 @@ # Define `authorize` and `authorize!` helpers module GraphQL + # rubocop:disable Metrics/MethodLength def self.assign_authorize(raise_unauthorized) + # rubocop:enable Metrics/MethodLength lambda do |defn, query = nil, policy: nil, record: nil| opts = {record: record, query: query || defn.name, diff --git a/lib/graphql-pundit/instrumenters/authorization.rb b/lib/graphql-pundit/instrumenters/authorization.rb index 5ae6fd0..23f48a4 100644 --- a/lib/graphql-pundit/instrumenters/authorization.rb +++ b/lib/graphql-pundit/instrumenters/authorization.rb @@ -25,15 +25,21 @@ def instrument(_type, field) end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/BlockLength def resolve_proc(current_user, old_resolve, options) - # rubocop:enable Metrics/MethodLength, Metrics/AbcSize lambda do |obj, args, ctx| begin result = if options[:proc] options[:proc].call(obj, args, ctx) else query = options[:query].to_s + '?' - record = options[:record] || obj + record = if options[:record].respond_to?(:call) + options[:record].call(obj, args, ctx) + else + options[:record] || obj + end policy = options[:policy] || record policy = ::Pundit::PolicyFinder.new(policy).policy! policy = policy.new(ctx[current_user], record) diff --git a/spec/graphql-pundit/instrumenters/authorization_spec.rb b/spec/graphql-pundit/instrumenters/authorization_spec.rb index 2c45364..5cf9bca 100644 --- a/spec/graphql-pundit/instrumenters/authorization_spec.rb +++ b/spec/graphql-pundit/instrumenters/authorization_spec.rb @@ -47,40 +47,84 @@ def test? RSpec.shared_examples 'field with authorization' do |error| context 'with query' do context 'with record' do - context 'with policy' do - let(:field) do - subj = subject - GraphQL::Field.define(type: 'String') do - name :notTest - if error - authorize! :test, policy: :test, record: subj.to_s - else - authorize :test, policy: :test, record: subj.to_s + context 'direct record' do + context 'with policy' do + let(:field) do + record = subject.to_s + GraphQL::Field.define(type: 'String') do + name :notTest + if error + authorize! :test, policy: :test, record: record + else + authorize :test, policy: :test, record: record + end + resolve ->(obj, _args, _ctx) { obj.to_s } end - resolve ->(obj, _args, _ctx) { obj.to_s } end + let(:result) { instrumented_field.resolve(Test.new('pass'), {}, {}) } + + include_examples 'an authorizing field', error end - let(:result) { instrumented_field.resolve(Test.new('pass'), {}, {}) } - include_examples 'an authorizing field', error + context 'without policy' do + let(:field) do + record = subject + GraphQL::Field.define(type: 'String') do + name :notTest + if error + authorize! :test, record: record + else + authorize :test, record: record + end + resolve ->(obj, _args, _ctx) { obj.to_s } + end + end + let(:result) { instrumented_field.resolve(Test.new('pass'), {}, {}) } + + include_examples 'an authorizing field', error + end end - context 'without policy' do - let(:field) do - subj = subject - GraphQL::Field.define(type: 'String') do - name :notTest - if error - authorize! :test, record: subj - else - authorize :test, record: subj + context 'lambda record' do + context 'with policy' do + let(:field) do + record = ->(_obj, _arguments, ctx) { ctx[:subject].to_s } + GraphQL::Field.define(type: 'String') do + name :notTest + if error + authorize! :test, policy: :test, record: record + else + authorize :test, policy: :test, record: record + end + resolve ->(obj, _args, _ctx) { obj.to_s } end - resolve ->(obj, _args, _ctx) { obj.to_s } end + let(:result) do + instrumented_field.resolve(Test.new('pass'), {}, subject: subject) + end + + include_examples 'an authorizing field', error end - let(:result) { instrumented_field.resolve(Test.new('pass'), {}, {}) } - include_examples 'an authorizing field', error + context 'without policy' do + let(:field) do + record = ->(_obj, _arguments, ctx) { ctx[:subject] } + GraphQL::Field.define(type: 'String') do + name :notTest + if error + authorize! :test, record: record + else + authorize :test, record: record + end + resolve ->(obj, _args, _ctx) { obj.to_s } + end + end + let(:result) do + instrumented_field.resolve(Test.new('pass'), {}, subject: subject) + end + + include_examples 'an authorizing field', error + end end end