filterer 0.4.3 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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