filterer 0.4.3 → 1.0.0.beta.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b96dd492a73ed7c13be50344b87d56a5600b2d4b
4
- data.tar.gz: 8042241a505eb55ce5adb1e7b158ba2faeac7f64
3
+ metadata.gz: b44892d5a150d19bef25b69c217cf1fa0fa76cb7
4
+ data.tar.gz: c8f02087f8d7ee437e936f841d23f9c2200f0462
5
5
  SHA512:
6
- metadata.gz: 4b1dfc79a3bef9c2c8583dba2221c7543d112484c4e201b8bd99117f7b109da624fdcf761fe439c79cd64abf6b07ee300363bf6d3f4a0731be1c68aaac807f14
7
- data.tar.gz: 2bfa646276b08ec0babdb6997621c8e735d622d65658d69b687c6787d5d6f33e0843f3142d7049c4cdd0cc6b8528213a833f7e4721fa8c4f7acff2212c09e219
6
+ metadata.gz: 3afe610c8e745b68f336a32078246cf94987a44803d8452384987acdd52886bfc4573ae33f146b12a1286e80deb901b487c15d37e8a671f4a302cd572e02f701
7
+ data.tar.gz: 79ca8ab44c8d266eed187033a2fb22f4554695af64489a75afccbcdeaba78f77d1d8322ed769ca94444607db864e9abbc0176130a0d40f47296f02d804ea0810
data/lib/filterer.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'filterer/engine'
2
2
  require 'filterer/base'
3
- require 'filterer/paginator'
4
3
 
5
4
  module Filterer
6
5
  end
@@ -0,0 +1,24 @@
1
+ module Filterer
2
+ module ActiveRecord
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def self.filter(params = {}, opts = {})
7
+ filterer_class(opts[:filterer_class]).
8
+ filter(params, { starting_query: all }.merge(opts))
9
+ end
10
+
11
+ def self.filterer_class(override)
12
+ if override
13
+ override.constantize
14
+ else
15
+ const_get("#{name}Filterer")
16
+ end
17
+ rescue
18
+ fail "Looked for #{name}Filterer and couldn't find one!"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ ActiveRecord::Base.send(:include, Filterer::ActiveRecord)
data/lib/filterer/base.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  module Filterer
2
2
  class Base
3
- IGNORED_PARAMS = %w(page)
4
-
5
- attr_accessor :results, :meta, :direction, :sort, :params, :opts
3
+ attr_accessor :results,
4
+ :meta,
5
+ :sort,
6
+ :params,
7
+ :opts
6
8
 
7
9
  class_attribute :sort_options
8
10
  self.sort_options = []
@@ -10,21 +12,22 @@ module Filterer
10
12
  class_attribute :per_page
11
13
  self.per_page = 20
12
14
 
13
- class_attribute :per_page_allow_override
14
- self.per_page_allow_override = false
15
+ class_attribute :allow_per_page_override
16
+ self.allow_per_page_override = false
15
17
 
16
18
  class_attribute :per_page_max
17
19
  self.per_page_max = 1000
18
20
 
19
21
  class << self
20
- def sort_option(key, query_string_or_proc = nil, opts = {})
21
- if query_string_or_proc.is_a?(Hash)
22
- opts, query_string_or_proc = query_string_or_proc.clone, nil
22
+ # Macro for adding sort options
23
+ def sort_option(key, string_or_proc = nil, opts = {})
24
+ if string_or_proc.is_a?(Hash)
25
+ opts, string_or_proc = string_or_proc.clone, nil
23
26
  end
24
27
 
25
- if !query_string_or_proc
28
+ if !string_or_proc
26
29
  if key.is_a?(String)
27
- query_string_or_proc = key
30
+ string_or_proc = key
28
31
  else
29
32
  raise 'Please provide a query string or a proc.'
30
33
  end
@@ -34,120 +37,128 @@ module Filterer
34
37
  raise "Default sort option can't have a Regexp key."
35
38
  end
36
39
 
37
- if query_string_or_proc.is_a?(Proc) && opts[:tiebreaker]
40
+ if string_or_proc.is_a?(Proc) && opts[:tiebreaker]
38
41
  raise "Tiebreaker can't be a proc."
39
42
  end
40
43
 
41
44
  self.sort_options += [{
42
45
  key: key,
43
- query_string_or_proc: query_string_or_proc,
46
+ string_or_proc: string_or_proc,
44
47
  opts: opts
45
48
  }]
46
49
  end
47
50
 
48
- def count(params = {}, opts = {})
49
- self.new(params, { meta_only: true }.merge(opts)).meta[:total]
51
+ # Public API
52
+ # @return [ActiveRecord::Association]
53
+ def filter(*args)
54
+ new(*args).results
50
55
  end
56
+ end
51
57
 
52
- def chain(params = {}, opts = {})
53
- self.new(params, { chainable: true }.merge(opts)).results
54
- end
58
+ def initialize(params = {}, opts = {})
59
+ self.params = defaults.merge(params).with_indifferent_access
60
+ self.opts = opts
61
+ self.results = opts[:starting_query] || starting_query
62
+ self.results = apply_default_filters || results
63
+ add_params_to_query
64
+ order_results unless opts[:skip_ordering]
65
+ paginate_results unless opts[:skip_pagination]
66
+ extend_active_record_relation
55
67
  end
56
68
 
57
69
  def defaults
58
70
  {}
59
71
  end
60
72
 
61
- def initialize(params = {}, opts = {})
62
- @params, @opts = defaults.merge(params).with_indifferent_access, opts
63
- setup_meta
64
- find_results
73
+ def starting_query
74
+ raise 'You must override this method!'
65
75
  end
66
76
 
67
- def paginator
68
- @paginator ||= Filterer::Paginator.new(self)
77
+ def direction
78
+ params[:direction].try(:downcase) == 'desc' ? 'desc' : 'asc'
69
79
  end
70
80
 
71
- def setup_meta
72
- @meta = {
73
- page: [@params[:page].to_i, 1].max,
74
- per_page: get_per_page
75
- }
81
+ def sort
82
+ @sort ||= begin
83
+ if params[:sort] && find_sort_option_from_param(params[:sort])
84
+ params[:sort]
85
+ else
86
+ default_sort_option.try(:[], :key)
87
+ end
88
+ end
76
89
  end
77
90
 
78
- def get_per_page
79
- if self.class.per_page_allow_override && @params[:per_page].present?
80
- [@params[:per_page].to_i, self.per_page_max].min
81
- else
82
- self.class.per_page
91
+ private
92
+
93
+ def paginate_results
94
+ if per_page && paginator
95
+ send("paginate_results_with_#{paginator}")
83
96
  end
84
97
  end
85
98
 
86
- def find_results
87
- @results = opts.delete(:starting_query) || starting_query
88
- add_params_to_query
89
- return if @opts[:chainable] && !@opts[:include_ordering]
90
- order_results
91
- return if @opts[:chainable]
92
- add_meta
93
- return if @opts[:meta_only]
99
+ def paginator
100
+ if defined?(Kaminari)
101
+ :kaminari
102
+ elsif defined?(WillPaginate)
103
+ :will_paginate
104
+ end
105
+ end
94
106
 
95
- # Add custom meta data if we've defined the method
96
- @meta.merge!(self.custom_meta_data) if self.respond_to?(:custom_meta_data)
107
+ def paginate_results_with_kaminari
108
+ self.results = results.page(current_page).per(per_page)
109
+ end
97
110
 
98
- # Return the paginated results
99
- @results = @results.limit(@meta[:per_page]).offset((@meta[:page] - 1)*@meta[:per_page])
111
+ def paginate_results_with_will_paginate
112
+ self.results = results.paginate(page: current_page, per_page: per_page)
100
113
  end
101
114
 
102
- def add_meta
103
- @meta[:total] = @results.unscope(:select).count
104
- @meta[:last_page] = [(@meta[:total].to_f / @meta[:per_page]).ceil, 1].max
105
- @meta[:page] = [@meta[:last_page], @meta[:page]].min
115
+ def apply_default_filters
116
+ results
106
117
  end
107
118
 
108
119
  def add_params_to_query
109
- @params.reject { |k, v| k.to_s.in?(IGNORED_PARAMS) }
110
- .select { |k, v| v.present? }
111
- .each do |k, v|
112
-
120
+ present_params.each do |k, v|
113
121
  method_name = "param_#{k}"
114
- @results = respond_to?(method_name) ? send(method_name, v) : @results
122
+
123
+ if respond_to?(method_name)
124
+ self.results = send(method_name, v)
125
+ end
115
126
  end
116
127
  end
117
128
 
118
- def order_results
119
- @direction = @params[:direction] == 'desc' ? 'DESC' : 'ASC'
120
- @sort = (params[:sort] && get_sort_option(params[:sort])) ? params[:sort] : default_sort_param
121
-
122
- if !get_sort_option(@sort)
123
- @results = @results.order default_sort_sql
124
- elsif get_sort_option(@sort)[:query_string_or_proc].is_a?(String)
125
- @results = @results.order basic_sort_sql
126
- elsif get_sort_option(@sort)[:query_string_or_proc].is_a?(Proc)
127
- apply_sort_proc
129
+ def present_params
130
+ params.select do |_k, v|
131
+ v.present?
128
132
  end
129
133
  end
130
134
 
131
- def default_sort_sql
132
- "#{@results.model.table_name}.id ASC"
135
+ def order_results
136
+ self.results = if !sort_option
137
+ results.order(default_sort_sql)
138
+ elsif sort_option[:string_or_proc].is_a?(String)
139
+ results.order(basic_sort_sql)
140
+ elsif sort_option[:string_or_proc].is_a?(Proc)
141
+ apply_sort_proc
142
+ end
143
+ end
144
+
145
+ def per_page
146
+ if self.class.allow_per_page_override && params[:per_page].present?
147
+ [params[:per_page], per_page_max].min
148
+ else
149
+ self.class.per_page
150
+ end
133
151
  end
134
152
 
135
- def basic_sort_sql
136
- %{
137
- #{get_sort_option(@sort)[:query_string_or_proc]}
138
- #{@direction}
139
- #{get_sort_option(@sort)[:opts][:nulls_last] ? 'NULLS LAST' : ''}
140
- #{tiebreaker_sort_string ? ',' + tiebreaker_sort_string : ''}
141
- }.squish
153
+ def current_page
154
+ [params[:page].to_i, 1].max
142
155
  end
143
156
 
144
- def apply_sort_proc
145
- sort_key = get_sort_option(@sort)[:key]
146
- matches = sort_key.is_a?(Regexp) && @sort.match(sort_key)
147
- @results = get_sort_option(@sort)[:query_string_or_proc].call(@results, matches, self)
157
+ def sort_option
158
+ @sort_option ||= find_sort_option_from_param(sort)
148
159
  end
149
160
 
150
- def get_sort_option(x)
161
+ def find_sort_option_from_param(x)
151
162
  self.class.sort_options.detect do |sort_option|
152
163
  if sort_option[:key].is_a?(Regexp)
153
164
  x.match(sort_option[:key])
@@ -157,20 +168,45 @@ module Filterer
157
168
  end
158
169
  end
159
170
 
160
- def default_sort_param
171
+ def default_sort_sql
172
+ "#{results.model.table_name}.id asc"
173
+ end
174
+
175
+ def basic_sort_sql
176
+ %{
177
+ #{sort_option[:string_or_proc]}
178
+ #{direction}
179
+ #{sort_option[:opts][:nulls_last] ? 'NULLS LAST' : ''}
180
+ #{tiebreaker_sort_string ? ', ' + tiebreaker_sort_string : ''}
181
+ }.squish
182
+ end
183
+
184
+ def apply_sort_proc
185
+ sort_key = sort_option[:key]
186
+ matches = sort_key.is_a?(Regexp) && params[:sort].match(sort_key)
187
+ sort_option[:string_or_proc].call(results, matches, self)
188
+ end
189
+
190
+ def default_sort_option
161
191
  self.class.sort_options.detect do |sort_option|
162
192
  sort_option[:opts][:default]
163
- end.try(:[], :key)
193
+ end
164
194
  end
165
195
 
166
196
  def tiebreaker_sort_string
167
197
  self.class.sort_options.detect do |sort_option|
168
198
  sort_option[:opts][:tiebreaker]
169
- end.try(:[], :query_string_or_proc)
199
+ end.try(:[], :string_or_proc)
170
200
  end
171
201
 
172
- def starting_query
173
- raise 'You must override this method!'
202
+ def extend_active_record_relation
203
+ results.instance_variable_set(:@filterer, self)
204
+
205
+ results.extending! do
206
+ def filterer
207
+ @filterer
208
+ end
209
+ end
174
210
  end
175
211
  end
176
212
  end
@@ -1,5 +1,9 @@
1
1
  module Filterer
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Filterer
4
+
5
+ ActiveSupport.on_load(:active_record) do
6
+ require 'filterer/active_record'
7
+ end
4
8
  end
5
9
  end
@@ -1,3 +1,3 @@
1
1
  module Filterer
2
- VERSION = '0.4.3'
2
+ VERSION = '1.0.0.beta.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filterer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 1.0.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Becker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-30 00:00:00.000000000 Z
11
+ date: 2015-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: benchmark-ips
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: capybara
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: rspec-rails
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -145,11 +173,10 @@ extensions: []
145
173
  extra_rdoc_files: []
146
174
  files:
147
175
  - Rakefile
148
- - app/helpers/filterer/pagination_helper.rb
149
176
  - lib/filterer.rb
177
+ - lib/filterer/active_record.rb
150
178
  - lib/filterer/base.rb
151
179
  - lib/filterer/engine.rb
152
- - lib/filterer/paginator.rb
153
180
  - lib/filterer/version.rb
154
181
  - lib/generators/filterer/filterer_generator.rb
155
182
  - lib/generators/filterer/templates/filter.rb
@@ -157,7 +184,6 @@ files:
157
184
  - lib/generators/rspec/templates/filter_spec.rb
158
185
  - lib/generators/test_unit/filterer_generator.rb
159
186
  - lib/generators/test_unit/templates/filter_test.rb
160
- - lib/tasks/filterer_tasks.rake
161
187
  homepage: https://github.com/dobtco/filterer
162
188
  licenses:
163
189
  - MIT
@@ -173,9 +199,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
199
  version: '0'
174
200
  required_rubygems_version: !ruby/object:Gem::Requirement
175
201
  requirements:
176
- - - ">="
202
+ - - ">"
177
203
  - !ruby/object:Gem::Version
178
- version: '0'
204
+ version: 1.3.1
179
205
  requirements: []
180
206
  rubyforge_project:
181
207
  rubygems_version: 2.2.2
@@ -1,54 +0,0 @@
1
- module Filterer
2
- module PaginationHelper
3
-
4
- RIGHT_ARROW = '&rsaquo;'.html_safe
5
- LEFT_ARROW = '&lsaquo;'.html_safe
6
-
7
- def render_filterer_pagination(filterer)
8
- content_tag(:div, class: 'pagination-wrapper') do
9
- content_tag(:ul, class: 'unstyled') do
10
- render_filterer_previous_link(filterer) +
11
- filterer.paginator.pages.map { |p| render_filterer_page_link(filterer, p) }.join('').html_safe +
12
- render_filterer_next_link(filterer)
13
- end
14
- end
15
- end
16
-
17
- private
18
- def calculate_filterer_pagination_url(page)
19
- url_for(params.merge(page: page))
20
- end
21
-
22
- def render_filterer_previous_link(filterer)
23
- content_tag(:li, class: filterer.meta[:page] == 1 ? "disabled" : '') do
24
- if filterer.meta[:page] == 1
25
- content_tag(:span) { LEFT_ARROW }
26
- else
27
- content_tag(:a, class: 'pagination-previous',
28
- href: calculate_filterer_pagination_url(filterer.meta[:page] - 1)) { LEFT_ARROW }
29
- end
30
- end
31
- end
32
-
33
- def render_filterer_next_link(filterer)
34
- content_tag(:li, class: filterer.meta[:page] == filterer.meta[:last_page] ? "disabled" : '') do
35
- if filterer.meta[:page] == filterer.meta[:last_page]
36
- content_tag(:span) { RIGHT_ARROW }
37
- else
38
- content_tag(:a, class: 'pagination-next', href: calculate_filterer_pagination_url(filterer.meta[:page] + 1)) { RIGHT_ARROW }
39
- end
40
- end
41
- end
42
-
43
- def render_filterer_page_link(filterer, p)
44
- if p == 'break'
45
- "<li><span>&hellip;</span></li>"
46
- else
47
- content_tag(:li, class: p == filterer.meta[:page] ? 'active' : '') do
48
- content_tag(:a, href: calculate_filterer_pagination_url(p)) { p.to_s }
49
- end
50
- end
51
- end
52
-
53
- end
54
- end
@@ -1,51 +0,0 @@
1
- module Filterer
2
- class Paginator
3
-
4
- attr_reader :pages
5
-
6
- def initialize(filterer)
7
- @filterer = filterer
8
- return @pages = [1] if @filterer.meta[:last_page] == 1
9
- push_default_pages
10
- calculate_additional_pages
11
- add_breaks
12
- end
13
-
14
- def push_default_pages
15
- @pages = [1, 2]
16
- push_page(@filterer.meta[:last_page], @filterer.meta[:last_page] - 1)
17
- end
18
-
19
- def calculate_additional_pages
20
- offset = 0
21
- current_page = @filterer.meta[:page]
22
-
23
- while @pages.length < 11 && ( (current_page - offset >= 1) || (current_page + offset <= @filterer.meta[:last_page]) ) do
24
- push_page(current_page - offset, current_page + offset)
25
- offset += 1
26
- end
27
- end
28
-
29
- def add_breaks
30
- pages_without_breaks = @pages.sort
31
- pages_with_breaks = []
32
-
33
- pages_without_breaks.each_with_index do |p, i|
34
- if pages_without_breaks[i - 1] && (p - pages_without_breaks[i - 1] > 1)
35
- pages_with_breaks.push 'break'
36
- end
37
-
38
- pages_with_breaks.push p
39
- end
40
-
41
- @pages = pages_with_breaks
42
- end
43
-
44
- def push_page(*args)
45
- args.each do |page|
46
- @pages.push(page) unless @pages.include?(page) || (page > @filterer.meta[:last_page]) || (page < 1)
47
- end
48
- end
49
-
50
- end
51
- end
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :filterer do
3
- # # Task goes here
4
- # end