might 0.3.0
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.
- 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
|