might 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.hound.yml +2 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +19 -0
  6. data/.travis.yml +14 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE +202 -0
  9. data/README.md +301 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +7 -0
  13. data/config/locales/en.yml +7 -0
  14. data/lib/might.rb +4 -0
  15. data/lib/might/fetcher.rb +252 -0
  16. data/lib/might/fetcher_error.rb +4 -0
  17. data/lib/might/filter_middleware.rb +28 -0
  18. data/lib/might/filter_parameter.rb +68 -0
  19. data/lib/might/filter_parameter_definition.rb +82 -0
  20. data/lib/might/filter_parameters_extractor.rb +78 -0
  21. data/lib/might/filter_parameters_validator.rb +26 -0
  22. data/lib/might/filter_predicates.rb +26 -0
  23. data/lib/might/filter_undefined_parameter.rb +15 -0
  24. data/lib/might/filter_value_validator.rb +43 -0
  25. data/lib/might/pagination_middleware.rb +60 -0
  26. data/lib/might/pagination_parameters_validator.rb +55 -0
  27. data/lib/might/paginator.rb +58 -0
  28. data/lib/might/railtie.rb +8 -0
  29. data/lib/might/ransackable_filter.rb +24 -0
  30. data/lib/might/ransackable_filter_parameters_adapter.rb +61 -0
  31. data/lib/might/ransackable_sort.rb +27 -0
  32. data/lib/might/ransackable_sort_parameters_adapter.rb +23 -0
  33. data/lib/might/result.rb +68 -0
  34. data/lib/might/sort_middleware.rb +31 -0
  35. data/lib/might/sort_parameter.rb +54 -0
  36. data/lib/might/sort_parameter_definition.rb +54 -0
  37. data/lib/might/sort_parameters_extractor.rb +62 -0
  38. data/lib/might/sort_parameters_validator.rb +26 -0
  39. data/lib/might/sort_undefined_parameter.rb +15 -0
  40. data/lib/might/sort_value_validator.rb +43 -0
  41. data/lib/might/version.rb +4 -0
  42. data/might.gemspec +33 -0
  43. metadata +240 -0
@@ -0,0 +1,27 @@
1
+ module Might
2
+ # Sort scope
3
+ class RansackableSort
4
+ # @param app [#call]
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ # @param env [<ActiveRecord::Relation, <String>]
10
+ # * first element is a scope to be sorted
11
+ # * second is a array with user provided sortings
12
+ # @return [<ActiveRecord::Relation, <String>]
13
+ #
14
+ def call(env)
15
+ scope, params = env
16
+
17
+ ransackable_query = scope.ransack
18
+ ransackable_query.sorts = params[:sort]
19
+
20
+ app.call([ransackable_query.result, params])
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :app
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Might
2
+ # Converts array of parameters to hash familiar to ransack gem
3
+ #
4
+ class RansackableSortParametersAdapter
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ scope, params = env
11
+
12
+ ransackable_parameters = Array(params[:sort]).map do |parameter|
13
+ "#{parameter.name} #{parameter.direction}"
14
+ end
15
+
16
+ app.call([scope, params.merge(sort: ransackable_parameters)])
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :app
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ module Might
2
+ # Marker module
3
+ module Result
4
+ end
5
+
6
+ # Represents fetching failure
7
+ class Failure
8
+ include Result
9
+
10
+ # @param errors [<String>]
11
+ def initialize(errors)
12
+ @errors = errors
13
+ end
14
+
15
+ # @param errors [<String>]
16
+ attr_reader :errors
17
+
18
+ # @return [true]
19
+ def failure?
20
+ !success?
21
+ end
22
+
23
+ # @return [false]
24
+ def success?
25
+ false
26
+ end
27
+
28
+ # @raise [NotImplementedError]
29
+ def get
30
+ fail NotImplementedError
31
+ end
32
+
33
+ # @yield given block
34
+ def get_or_else
35
+ yield
36
+ end
37
+ end
38
+
39
+ # Represents fetching success
40
+ class Success
41
+ include Result
42
+
43
+ # @param value [ActiveRecord::Relation]
44
+ def initialize(value)
45
+ @value = value
46
+ end
47
+
48
+ # @return [false]
49
+ def failure?
50
+ !success?
51
+ end
52
+
53
+ # @return [true]
54
+ def success?
55
+ true
56
+ end
57
+
58
+ # @return [ActiveRecord::Relation]
59
+ def get
60
+ @value
61
+ end
62
+
63
+ # @return [ActiveRecord::Relation]
64
+ def get_or_else
65
+ @value
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,31 @@
1
+ require 'might/ransackable_sort_parameters_adapter'
2
+ require 'might/ransackable_sort'
3
+ require 'might/sort_parameters_extractor'
4
+ require 'middleware'
5
+
6
+ module Might
7
+ # Sort scope using ransack gem
8
+ #
9
+ class SortMiddleware
10
+ # @param app [#call]
11
+ #
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ attr_reader :app
17
+
18
+ # @param [Array(ActiveRecord::Relation, Hash)] env
19
+ # First argument is a ActiveRecord relation which must be sorted
20
+ # Second argument is a request parameters provided by user
21
+ #
22
+ def call(env)
23
+ scope, = ::Middleware::Builder.new do |b|
24
+ b.use RansackableSortParametersAdapter
25
+ b.use RansackableSort
26
+ end.call(env)
27
+
28
+ app.call([scope, env[1]])
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Might
4
+ # User provided filtering on particular parameter
5
+ #
6
+ class SortParameter
7
+ DIRECTIONS = %w(asc desc).freeze
8
+ REVERSED_DIRECTIONS = {
9
+ 'asc' => 'desc',
10
+ 'desc' => 'asc'
11
+ }.freeze
12
+
13
+ attr_reader :direction
14
+
15
+ # @return [ParameterDefinition]
16
+ #
17
+ attr_reader :definition
18
+
19
+ # @param ['asc', desc'] direction
20
+ # @param [SortParameterDefinition]
21
+ #
22
+ def initialize(direction, definition)
23
+ @direction = direction.to_s
24
+ fail ArgumentError unless DIRECTIONS.include?(@direction)
25
+ @definition = definition
26
+ @validator = definition.validator
27
+ end
28
+
29
+ attr_reader :validator
30
+ delegate :name, to: :definition
31
+ delegate :valid?, to: :validator
32
+ delegate :invalid?, to: :validator
33
+
34
+ def errors
35
+ validator.errors.full_messages
36
+ end
37
+
38
+ def ==(other)
39
+ is_a?(other.class) &&
40
+ direction == other.direction &&
41
+ definition == other.definition
42
+ end
43
+
44
+ # @return ['asc', desc']
45
+ #
46
+ def direction
47
+ if definition.reverse_direction?
48
+ REVERSED_DIRECTIONS[@direction]
49
+ else
50
+ @direction
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ require_relative 'sort_value_validator'
2
+
3
+ module Might
4
+ # Sorting parameter definition
5
+ #
6
+ class SortParameterDefinition
7
+ # @return [String]
8
+ attr_reader :name
9
+
10
+ # If the property name doesn't match the name in the query string, use the :as option
11
+ # @return [String]
12
+ attr_reader :as
13
+
14
+ # @return [Boolean]
15
+ attr_reader :reverse_direction
16
+
17
+ # @param [String] name of the field
18
+ # @param [String] as (#name) alias for the property
19
+ # @param [Boolean] reverse_direction (false) default sorting direction
20
+ #
21
+ def initialize(name, as: name, reverse_direction: false)
22
+ @name = name.to_s
23
+ @as = as.to_s
24
+ @reverse_direction = reverse_direction
25
+ end
26
+
27
+ # If two parameters have the same name, they are equal.
28
+ delegate :hash, to: :name
29
+
30
+ def eql?(other)
31
+ other.is_a?(self.class) && other.name == name
32
+ end
33
+
34
+ def ==(other)
35
+ other.is_a?(self.class) &&
36
+ other.name == name &&
37
+ other.as == as
38
+ end
39
+
40
+ alias_method :reverse_direction?, :reverse_direction
41
+
42
+ def validator
43
+ SortValueValidator.build(self).new
44
+ end
45
+
46
+ def defined?
47
+ true
48
+ end
49
+
50
+ def undefined?
51
+ !self.defined?
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,62 @@
1
+ require 'might/sort_undefined_parameter'
2
+ require 'might/sort_parameter'
3
+
4
+ module Might
5
+ # User provided sorting syntax:
6
+ # * `name` - sort by name
7
+ # * `-name` - sort by name in reversed order
8
+ # * `-name,created_at` - sort by name in reversed order, and then sort by created_at
9
+ #
10
+ # This middleware parses sorting string and builds Parameter array
11
+ # @see Might::Sort::Parameter
12
+ #
13
+ # If user passes not defined sort order, it yields to `UndefinedParameter`, so you may
14
+ # validate it.
15
+ #
16
+ class SortParametersExtractor
17
+ # @param app [#call]
18
+ # @param parameters_definition [Set<Might::SortParameterDefinition>]
19
+ def initialize(app, parameters_definition)
20
+ @app = app
21
+ @parameters_definition = parameters_definition
22
+ end
23
+
24
+ # @param env [<String, []>]
25
+ # * first element is a scope to be sorted
26
+ # * second is a String with user provided sortings
27
+ # @return [<<Might::RansackableSort::SortParameter, []>]
28
+ #
29
+ def call(env)
30
+ params, errors = env
31
+
32
+ sort_params = sort_order(params[:sort]).map do |(attribute, direction)|
33
+ extract_parameter(attribute, direction)
34
+ end
35
+
36
+ app.call([params.merge(sort: sort_params), errors])
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :parameters_definition, :app
42
+
43
+ def extract_parameter(name, direction)
44
+ definition = parameters_definition.detect { |d| d.as == name } || SortUndefinedParameter.new(name)
45
+ SortParameter.new(direction, definition)
46
+ end
47
+
48
+ def sort_order(params)
49
+ String(params).split(',').map do |attribute|
50
+ sorting_for(attribute)
51
+ end
52
+ end
53
+
54
+ def sorting_for(field)
55
+ if field.start_with?('-')
56
+ [field.delete('-'), 'desc']
57
+ else
58
+ [field, 'asc']
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,26 @@
1
+ module Might
2
+ # Validates sortings and raises error if one of them is invalid
3
+ #
4
+ class SortParametersValidator
5
+ # @param app [#call]
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ # @param env [<{:sort => Might::FilterParameter}, Array>]
11
+ # @return [<{:sort => Might::FilterParameter}, Array>]
12
+ #
13
+ def call(env)
14
+ params, errors = env
15
+
16
+ not_allowed_parameters = Array(params[:sort]).select(&:invalid?)
17
+ messages = not_allowed_parameters.flat_map(&:errors)
18
+
19
+ app.call([params, errors.concat(messages)])
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :app
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ require 'might/sort_parameter_definition'
2
+
3
+ module Might
4
+ # Null object for ParameterDefinition
5
+ #
6
+ class SortUndefinedParameter < SortParameterDefinition
7
+ def required?
8
+ false
9
+ end
10
+
11
+ def defined?
12
+ false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/module/delegation'
3
+ require 'active_model/validator'
4
+ require 'active_model/validations'
5
+ require 'active_model/callbacks'
6
+ require 'active_model/naming'
7
+ require 'active_model/translation'
8
+ require 'active_model/errors'
9
+
10
+ module Might
11
+ # Build singleton validation class for specified attribute name
12
+ # @example you need a nice validator for a first_name
13
+ # validator_klass = ValueValidator.build('first_name', presence: true, length: { minimum: 3 })
14
+ # validator = validator_klass.new('Bo')
15
+ # validator.valid? #=> false
16
+ # validator.errors.full_messages #=> ['First name is too short (minimum is 3 characters)']
17
+ #
18
+ module SortValueValidator
19
+ module_function
20
+
21
+ # Validates if Parameter is undefined or not
22
+ class DefinedValidator < ActiveModel::EachValidator
23
+ def validate_each(record, attribute, _value)
24
+ record.errors.add(attribute, :undefined_sort_order) if record.undefined?
25
+ end
26
+ end
27
+
28
+ def build(definition)
29
+ Class.new do
30
+ include ActiveModel::Validations
31
+
32
+ validates(definition.name, 'might/sort_value_validator/defined': true)
33
+
34
+ define_method(:undefined?) { definition.undefined? }
35
+ define_method(definition.name) {}
36
+
37
+ def self.model_name
38
+ ActiveModel::Name.new(Might, nil, 'Might')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ #
2
+ module Might
3
+ VERSION = '0.3.0'
4
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'might/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'might'
8
+ spec.version = Might::VERSION
9
+ spec.authors = ['Tema Bolshakov']
10
+ spec.email = ['abolshakov@spbtv.com']
11
+
12
+ spec.summary = 'Mighty resource fetchers'
13
+ spec.description = 'Mighty resource fetchers build on top of Ransack gem'
14
+ spec.homepage = 'https://github.com/SPBTV/might'
15
+ spec.license = 'Apache 2.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_runtime_dependency 'activesupport', '>= 3.2'
23
+ spec.add_runtime_dependency 'activemodel', '>= 3.2'
24
+ spec.add_runtime_dependency 'uber', '~> 0.0.15'
25
+ spec.add_runtime_dependency 'ibsciss-middleware', '~> 0.3'
26
+ spec.add_runtime_dependency 'ransack', '~> 1.6.6'
27
+ spec.add_development_dependency 'activerecord', '>= 3.2'
28
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
29
+ spec.add_development_dependency 'bundler', '~> 1.10'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.3'
32
+ spec.add_development_dependency 'rubocop', '~> 0.34.2'
33
+ end