might 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.hound.yml +2 -0
- data/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/.travis.yml +14 -0
- data/Gemfile +6 -0
- data/LICENSE +202 -0
- data/README.md +301 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/config/locales/en.yml +7 -0
- data/lib/might.rb +4 -0
- data/lib/might/fetcher.rb +252 -0
- data/lib/might/fetcher_error.rb +4 -0
- data/lib/might/filter_middleware.rb +28 -0
- data/lib/might/filter_parameter.rb +68 -0
- data/lib/might/filter_parameter_definition.rb +82 -0
- data/lib/might/filter_parameters_extractor.rb +78 -0
- data/lib/might/filter_parameters_validator.rb +26 -0
- data/lib/might/filter_predicates.rb +26 -0
- data/lib/might/filter_undefined_parameter.rb +15 -0
- data/lib/might/filter_value_validator.rb +43 -0
- data/lib/might/pagination_middleware.rb +60 -0
- data/lib/might/pagination_parameters_validator.rb +55 -0
- data/lib/might/paginator.rb +58 -0
- data/lib/might/railtie.rb +8 -0
- data/lib/might/ransackable_filter.rb +24 -0
- data/lib/might/ransackable_filter_parameters_adapter.rb +61 -0
- data/lib/might/ransackable_sort.rb +27 -0
- data/lib/might/ransackable_sort_parameters_adapter.rb +23 -0
- data/lib/might/result.rb +68 -0
- data/lib/might/sort_middleware.rb +31 -0
- data/lib/might/sort_parameter.rb +54 -0
- data/lib/might/sort_parameter_definition.rb +54 -0
- data/lib/might/sort_parameters_extractor.rb +62 -0
- data/lib/might/sort_parameters_validator.rb +26 -0
- data/lib/might/sort_undefined_parameter.rb +15 -0
- data/lib/might/sort_value_validator.rb +43 -0
- data/lib/might/version.rb +4 -0
- data/might.gemspec +33 -0
- 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
|
data/lib/might/result.rb
ADDED
@@ -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,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
|
data/might.gemspec
ADDED
@@ -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
|