toller 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4ac81008c0c129af6b87f8021fa9f9b6c11b88b6023d6ce2775c74ed384ce68d
4
+ data.tar.gz: 99355eb92d24c18a5b59fde10658463aeea8d1a075cce28da6408b5b4757c86e
5
+ SHA512:
6
+ metadata.gz: f373b0612a6d960bbc2dfcc3975cf8e9e2e5c9346836f99f98f7607ce96f36eaa627b3f3116f18d699b158e4410a80ce85c33d959202798bc0c6b11f76b719ab
7
+ data.tar.gz: a3d9ba5fe8083d69a79a8853e9ad49a8abe8fae09d9a5106580362ef433ff60c899a964b2a4c0827af3e7375f3a1ed220671e55f3c526b450742db96cf449be8
@@ -0,0 +1,20 @@
1
+ Copyright 2020 David Freerksen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,68 @@
1
+ # Toller
2
+
3
+ URL based filtering and sorting. See the wiki for usage information.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'toller'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ ## Filtering
20
+
21
+ Filters are not automagically set up for you. You define the filters you want.
22
+
23
+ ### Filter Types
24
+
25
+ * integer - Filter on an integer column
26
+ * boolean - Filter on a boolean column
27
+ * string - Filter on a string column
28
+ * text - Filter on a text column
29
+ * date - Filter on a date column
30
+ * time - Filter on a time column
31
+ * datetime - Filter on a datetime column
32
+ * scope - Filter on an ActiveRecord scope
33
+
34
+ ## Sort
35
+
36
+ Sorting is not automagically set up for you. You define the sorting you want.
37
+
38
+ Sort parameters are passed in the URL as such `?sort=position`. Multiple sort parameters can be passed like so `?sort=-published_at,title`.
39
+
40
+ ### Sort Types
41
+
42
+ Every sort must have a type. Valid sort types are:
43
+
44
+ * integer - Sort on an integer column
45
+ * string - Sort on a string column
46
+ * text - Sort on a text column
47
+ * date - Sort on a date column
48
+ * time - Sort on a time column
49
+ * datetime - Sort on a datetime column
50
+ * scope - Sort on an ActiveRecord scope
51
+
52
+ ## Testing
53
+
54
+ ```bash
55
+ $ bin/test
56
+ ```
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it ([https://github.com/dfrerksen/recieve/fork](https://github.com/dfrerksen/recieve/fork))
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create a new Pull Request
65
+
66
+ ## License
67
+
68
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'bundler/gem_tasks'
10
+
11
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
12
+ load 'rails/tasks/engine.rake'
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'toller/filter'
4
+ require 'toller/filters/mutators/boolean'
5
+ require 'toller/filters/mutators/date'
6
+ require 'toller/filters/mutators/datetime'
7
+ require 'toller/filters/mutators/integer'
8
+ require 'toller/filters/mutators/time'
9
+ require 'toller/filters/scope_handler'
10
+ require 'toller/filters/where_handler'
11
+ require 'toller/filtrator'
12
+ require 'toller/sort'
13
+ require 'toller/sorts/order_handler'
14
+ require 'toller/sorts/scope_handler'
15
+
16
+ ##
17
+ # Toller
18
+ #
19
+ # Query param based filtering and sorting
20
+ #
21
+ module Toller
22
+ extend ActiveSupport::Concern
23
+
24
+ def filtrate(collection)
25
+ Filtrator.filter(collection, filter_params, sort_params, retrievals)
26
+ end
27
+
28
+ included do
29
+ # none
30
+ end
31
+
32
+ class_methods do
33
+ def filter_on(parameter, type:, **options)
34
+ _filters << Filter.new(parameter, type, options)
35
+ end
36
+
37
+ def sort_on(parameter, type:, **options)
38
+ _filters << Sort.new(parameter, type, options)
39
+ end
40
+
41
+ def _filters
42
+ @_filters ||= []
43
+ end
44
+ end
45
+
46
+ def filter_params
47
+ params.fetch(filter_param_key.to_sym, {})
48
+ end
49
+
50
+ def sort_params
51
+ params.fetch(sort_param_key.to_sym, '').split(',')
52
+ end
53
+
54
+ def filter_param_key
55
+ :filters
56
+ end
57
+
58
+ def sort_param_key
59
+ :sort
60
+ end
61
+
62
+ private
63
+
64
+ def retrievals
65
+ self.class.ancestors.flat_map { |klass| klass.try(:_filters) }.compact
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ ##
5
+ # Filter
6
+ #
7
+ class Filter
8
+ attr_reader :parameter, :properties, :type
9
+
10
+ def initialize(parameter, type, options)
11
+ @parameter = parameter
12
+ @type = type
13
+ @properties = options.reverse_merge(
14
+ field: parameter,
15
+ default: false,
16
+ scope_name: nil
17
+ )
18
+ end
19
+
20
+ def apply!(collection, value)
21
+ if type == :scope
22
+ Filters::ScopeHandler.new.call(collection, value, properties)
23
+ else
24
+ Filters::WhereHandler.new.call(collection, type, value, properties)
25
+ end
26
+ end
27
+
28
+ def default
29
+ properties[:default]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ module Mutators
6
+ ##
7
+ # Boolean filter mutator
8
+ #
9
+ module Boolean
10
+ module_function
11
+
12
+ def call(value)
13
+ %w[1 t true y yes].include?(value)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ module Mutators
6
+ ##
7
+ # Date filter mutator
8
+ #
9
+ module Date
10
+ module_function
11
+
12
+ def call(value)
13
+ range_dots = inclusive_or_exclusive_range(value)
14
+
15
+ return value if range_dots.blank?
16
+
17
+ range(value, range_dots)
18
+ end
19
+
20
+ def range(value, dots)
21
+ Range.new(*value.split(dots))
22
+ end
23
+
24
+ def inclusive_or_exclusive_range(value)
25
+ return '...' if value.include?('...')
26
+ return '..' if value.include?('..')
27
+
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ module Mutators
6
+ ##
7
+ # Datetime filter mutator
8
+ #
9
+ module Datetime
10
+ module_function
11
+
12
+ def call(value)
13
+ range_dots = inclusive_or_exclusive_range(value)
14
+
15
+ return value if range_dots.blank?
16
+
17
+ range(value, range_dots)
18
+ end
19
+
20
+ def range(value, dots)
21
+ Range.new(*value.split(dots))
22
+ end
23
+
24
+ def inclusive_or_exclusive_range(value)
25
+ return '...' if value.include?('...')
26
+ return '..' if value.include?('..')
27
+
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ module Mutators
6
+ ##
7
+ # Integer filter mutator
8
+ #
9
+ module Integer
10
+ module_function
11
+
12
+ def call(value)
13
+ return value unless range?(value)
14
+
15
+ range(value)
16
+ end
17
+
18
+ def range?(value)
19
+ range_dots = inclusive_or_exclusive_range(value)
20
+
21
+ range_dots.present?
22
+ end
23
+
24
+ def range(value)
25
+ Range.new(*value.split(inclusive_or_exclusive_range(value)))
26
+ end
27
+
28
+ def inclusive_or_exclusive_range(value)
29
+ return '...' if value.include?('...')
30
+ return '..' if value.include?('..')
31
+
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ module Mutators
6
+ ##
7
+ # Time filter mutator
8
+ #
9
+ module Time
10
+ module_function
11
+
12
+ def call(value)
13
+ range_dots = inclusive_or_exclusive_range(value)
14
+
15
+ return value if range_dots.blank?
16
+
17
+ range(value, range_dots)
18
+ end
19
+
20
+ def range(value, dots)
21
+ Range.new(*value.split(dots))
22
+ end
23
+
24
+ def inclusive_or_exclusive_range(value)
25
+ return '...' if value.include?('...')
26
+ return '..' if value.include?('..')
27
+
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ ##
6
+ # Scope handler for filter
7
+ #
8
+ class ScopeHandler
9
+ def call(collection, value, properties)
10
+ scoped_name = properties[:scope_name] || properties[:field]
11
+
12
+ collection.public_send(scoped_name, value)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Filters
5
+ ##
6
+ # Where handler for filter
7
+ #
8
+ class WhereHandler
9
+ def call(collection, type, value, properties)
10
+ field_name = properties[:field]
11
+
12
+ mutated_value = value_mutator(type, value)
13
+
14
+ collection.where(field_name => mutated_value)
15
+ end
16
+
17
+ private
18
+
19
+ def value_mutator(type, value)
20
+ return value unless %i[boolean date datetime integer time].include?(type)
21
+
22
+ send("#{type}_mutator", value)
23
+ end
24
+
25
+ def boolean_mutator(value)
26
+ Mutators::Boolean.call(value)
27
+ end
28
+
29
+ def integer_mutator(value)
30
+ Mutators::Integer.call(value)
31
+ end
32
+
33
+ def date_mutator(value)
34
+ Mutators::Date.call(value)
35
+ end
36
+
37
+ def time_mutator(value)
38
+ Mutators::Time.call(value)
39
+ end
40
+
41
+ def datetime_mutator(value)
42
+ Mutators::Datetime.call(value)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ ##
5
+ # Filtrator
6
+ #
7
+ class Filtrator
8
+ attr_reader :collection, :filter_params, :retrievals, :sort_params
9
+
10
+ def self.filter(collection, filter_params, sort_params, retrievals)
11
+ new(collection, filter_params, sort_params, retrievals).filter
12
+ end
13
+
14
+ def initialize(collection, filter_params, sort_params, retrievals)
15
+ @collection = collection
16
+ @filter_params = filter_params
17
+ @sort_params = sort_params
18
+ @retrievals = retrievals
19
+ end
20
+
21
+ def filter
22
+ active_retrievals.reduce(collection) do |items, retrieval|
23
+ param_value = if retrieval.is_a?(Filter)
24
+ filter_params.fetch(retrieval.parameter, nil)
25
+ else
26
+ sort_params.include?("-#{retrieval.parameter}") ? :desc : :asc
27
+ end
28
+
29
+ retrieval.apply!(items, param_value)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def active_retrievals
36
+ retrievals.select do |retrieval|
37
+ retrieval.is_a?(Filter) ? filtering_activated?(retrieval) : sorting_activated?(retrieval)
38
+ end
39
+ end
40
+
41
+ def filtering_activated?(retrieval)
42
+ return true if filter_params.blank? && retrieval.default
43
+
44
+ filter_params.fetch(retrieval.parameter, nil).present?
45
+ end
46
+
47
+ def sorting_activated?(retrieval)
48
+ return true if sort_params.blank? && retrieval.default
49
+
50
+ string_parameter = retrieval.parameter.to_s
51
+
52
+ sort_params.include?(string_parameter) || sort_params.include?("-#{string_parameter}")
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ ##
5
+ # Sort
6
+ #
7
+ class Sort
8
+ attr_reader :parameter, :properties, :type
9
+
10
+ def initialize(parameter, type, options)
11
+ @parameter = parameter
12
+ @type = type
13
+ @properties = options.reverse_merge(
14
+ field: parameter,
15
+ default: false,
16
+ scope_name: nil
17
+ )
18
+ end
19
+
20
+ def apply!(collection, direction = :asc)
21
+ if type == :scope
22
+ Sorts::ScopeHandler.new.call(collection, direction, properties)
23
+ else
24
+ Sorts::OrderHandler.new.call(collection, direction, properties)
25
+ end
26
+ end
27
+
28
+ def default
29
+ properties[:default]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Sorts
5
+ ##
6
+ # Order handler for filter
7
+ #
8
+ class OrderHandler
9
+ def call(collection, direction, properties)
10
+ field_name = properties[:field]
11
+
12
+ collection.order(field_name => direction)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ module Sorts
5
+ ##
6
+ # Scope handler for sort
7
+ #
8
+ class ScopeHandler
9
+ def call(collection, direction, properties)
10
+ scoped_name = properties[:scope_name] || properties[:field]
11
+
12
+ collection.public_send(scoped_name, direction)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toller
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Freerksen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.3
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.3.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.3
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.3.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: Description of Toller.
48
+ email:
49
+ - dfreerksen@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - MIT-LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - lib/toller.rb
58
+ - lib/toller/filter.rb
59
+ - lib/toller/filters/mutators/boolean.rb
60
+ - lib/toller/filters/mutators/date.rb
61
+ - lib/toller/filters/mutators/datetime.rb
62
+ - lib/toller/filters/mutators/integer.rb
63
+ - lib/toller/filters/mutators/time.rb
64
+ - lib/toller/filters/scope_handler.rb
65
+ - lib/toller/filters/where_handler.rb
66
+ - lib/toller/filtrator.rb
67
+ - lib/toller/sort.rb
68
+ - lib/toller/sorts/order_handler.rb
69
+ - lib/toller/sorts/scope_handler.rb
70
+ - lib/toller/version.rb
71
+ homepage: https://github.com/dfreerksen/toller
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.0.3
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Summary of Toller.
94
+ test_files: []