filterrific 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +16 -1
- data/{LICENSE → MIT-LICENSE} +1 -1
- data/README.md +118 -0
- data/Rakefile +9 -54
- data/doc/Overview diagram.graffle/data.plist +1012 -1296
- data/doc/development_notes/controller_api.txt +8 -1
- data/doc/meta.md +27 -0
- data/lib/filterrific.rb +3 -9
- data/lib/filterrific/action_view_extension.rb +15 -0
- data/lib/filterrific/active_record_extension.rb +71 -0
- data/lib/filterrific/engine.rb +26 -0
- data/lib/filterrific/param_set.rb +31 -22
- data/lib/filterrific/version.rb +3 -0
- data/spec/action_view_extension_spec.rb +16 -0
- data/spec/active_record_extension_spec.rb +85 -0
- data/spec/filterrific_spec.rb +5 -0
- data/spec/param_set_spec.rb +109 -0
- data/spec/spec_helper.rb +6 -0
- data/vendor/assets/images/filterrific-spinner.gif +0 -0
- data/vendor/assets/javascripts/filterrific-jquery.js +97 -0
- metadata +135 -86
- data/README.rdoc +0 -19
- data/VERSION +0 -1
- data/doc/Overview diagram.graffle/QuickLook/Preview.pdf +0 -0
- data/doc/Overview diagram.graffle/QuickLook/Thumbnail.tiff +0 -0
- data/doc/documentation.md +0 -34
- data/doc/ideas.txt +0 -107
- data/doc/todo.md +0 -21
- data/doc/workflow.md +0 -17
- data/filterrific.gemspec +0 -75
- data/lib/filterrific/model_mixin.rb +0 -48
- data/lib/filterrific/railtie.rb +0 -25
- data/lib/filterrific/view_helpers.rb +0 -35
- data/test/helper.rb +0 -10
- data/test/test_filterrific.rb +0 -7
@@ -20,8 +20,15 @@ def index
|
|
20
20
|
* :debug => false # if true, prints out debug info. This also exists in view helper. Maybe one to
|
21
21
|
logger/STDOUT, the other to view?
|
22
22
|
* :param_prefix => "filterrific" # the param prefix used to shuttle params between view and controller.
|
23
|
-
|
23
|
+
|
24
24
|
@publications = Publication.filterrific_find(@filterrific).paginate....
|
25
25
|
@publications = current_user.publications.filterrific_find(@filterrific).paginate....
|
26
26
|
...
|
27
27
|
end
|
28
|
+
|
29
|
+
@filterrific = Filterrific.new(User, params_hash)
|
30
|
+
@users = User.filterrific_find(@filterrific).where(...)
|
31
|
+
|
32
|
+
@filterrific = Filterrific.new(self, User)
|
33
|
+
@filterrific = filterrific_init(User, options) (loads and persists in session)
|
34
|
+
@users = User.filterrific_find(@filterrific).paginate(:page => params[:page])
|
data/doc/meta.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Workflow to Maintain This Gem
|
2
|
+
=============================
|
3
|
+
|
4
|
+
I use the gem-release gem
|
5
|
+
|
6
|
+
For more info see: https://github.com/svenfuchs/gem-release#usage
|
7
|
+
|
8
|
+
Steps for an update
|
9
|
+
-------------------
|
10
|
+
|
11
|
+
1. Update code and commit it.
|
12
|
+
2. Add entry to CHANGELOG:
|
13
|
+
* h1 for major release
|
14
|
+
* h2 for minor release
|
15
|
+
* h3 for patch release
|
16
|
+
3. Bump the version with one of these commands:
|
17
|
+
* `gem bump --version 1.1.1` # Bump the gem version to the given version number
|
18
|
+
* `gem bump --version major` # 0.0.1 -> 1.0.0
|
19
|
+
* `gem bump --version minor` # 0.0.1 -> 0.1.0
|
20
|
+
* `gem bump --version patch` # 0.0.1 -> 0.0.2
|
21
|
+
4. Release it.
|
22
|
+
* `gem release`
|
23
|
+
5. Create a git tag and push to origin.
|
24
|
+
`gem tag`
|
25
|
+
|
26
|
+
|
27
|
+
http://prioritized.net/blog/gemify-assets-for-rails/
|
data/lib/filterrific.rb
CHANGED
@@ -1,11 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Filterrific
|
4
|
-
|
5
|
-
if defined?(Rails) && Rails::VERSION::MAJOR == 3
|
6
|
-
require 'filterrific/railtie'
|
7
|
-
else
|
8
|
-
raise "Filterrific requires Rails 3"
|
9
|
-
end
|
1
|
+
require 'filterrific/version'
|
2
|
+
require 'filterrific/engine'
|
10
3
|
|
4
|
+
module Filterrific
|
11
5
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Adds view helpers to ActionView
|
3
|
+
#
|
4
|
+
module Filterrific::ActionViewExtension
|
5
|
+
|
6
|
+
# Renders a spinner while the list is being updated
|
7
|
+
def render_filterrific_spinner
|
8
|
+
%(
|
9
|
+
<span class="filterrific_spinner" style="display:none;">
|
10
|
+
#{ image_tag('filterrific-spinner.gif') }
|
11
|
+
</span>
|
12
|
+
).html_safe
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#
|
2
|
+
# Adds filterrific methods to ActiveRecord::Base and sub classes.
|
3
|
+
#
|
4
|
+
require 'filterrific/param_set'
|
5
|
+
|
6
|
+
module Filterrific::ActiveRecordExtension
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Adds filterrific behavior to class when called like so:
|
11
|
+
#
|
12
|
+
# filterrific(
|
13
|
+
# :default_settings => { :sorted_by => "created_at_asc" },
|
14
|
+
# :filter_names => [:sorted_by, :search_query, :with_state]
|
15
|
+
# )
|
16
|
+
#
|
17
|
+
# @params[Hash] options
|
18
|
+
# Required keys are:
|
19
|
+
# * :filter_names: a list of filter_names to be exposed by Filterrific
|
20
|
+
# Optional keys are:
|
21
|
+
# * :default_settings: default filter settings
|
22
|
+
def filterrific(options)
|
23
|
+
cattr_accessor :filterrific_default_settings
|
24
|
+
cattr_accessor :filterrific_filter_names
|
25
|
+
|
26
|
+
options.stringify_keys!
|
27
|
+
|
28
|
+
# Raise exception if not filter_names are given
|
29
|
+
self.filterrific_filter_names = (
|
30
|
+
options['filter_names'] || options['scope_names'] || []
|
31
|
+
).map { |e| e.to_s }
|
32
|
+
raise(ArgumentError, ":filter_names can't be empty") if filterrific_filter_names.blank?
|
33
|
+
|
34
|
+
self.filterrific_default_settings = (
|
35
|
+
options['default_settings'] || options['defaults'] || {}
|
36
|
+
).stringify_keys
|
37
|
+
# Raise exception if defaults contain keys that are not present in filter_names
|
38
|
+
if (
|
39
|
+
invalid_defaults = (filterrific_default_settings.keys - filterrific_filter_names)
|
40
|
+
).any?
|
41
|
+
raise(ArgumentError, "Invalid default keys: #{ invalid_defaults.inspect }")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns ActiveRecord relation based on given filterrific_param_set.
|
46
|
+
# Use like so:
|
47
|
+
# ModelClass.filterrific_find(@filterrific_param_set)
|
48
|
+
#
|
49
|
+
# @param[Filterrific::ParamSet] filterrific_param_set
|
50
|
+
# @return[ActiveRecord::Relation] an ActiveRecord relation.
|
51
|
+
def filterrific_find(filterrific_param_set)
|
52
|
+
unless filterrific_param_set.is_a?(Filterrific::ParamSet)
|
53
|
+
raise(ArgumentError, "Invalid Filterrific::ParamSet: #{ filterrific_param_set.inspect }")
|
54
|
+
end
|
55
|
+
|
56
|
+
# set initial ar_proxy to including class
|
57
|
+
ar_proxy = self
|
58
|
+
|
59
|
+
# apply filterrific params
|
60
|
+
self.filterrific_filter_names.each do |filter_name|
|
61
|
+
filter_param = filterrific_param_set.send(filter_name)
|
62
|
+
next if filter_param.blank? # skip blank filter_params
|
63
|
+
ar_proxy = ar_proxy.send(filter_name, filter_param)
|
64
|
+
end
|
65
|
+
|
66
|
+
ar_proxy
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module Filterrific
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
|
6
|
+
# It's an engine so that we can add javascript and image assets
|
7
|
+
# to the asset pipeline.
|
8
|
+
|
9
|
+
require 'filterrific/param_set'
|
10
|
+
|
11
|
+
initializer "filterrific.active_record_extension" do |app|
|
12
|
+
require 'filterrific/active_record_extension'
|
13
|
+
class ::ActiveRecord::Base
|
14
|
+
extend Filterrific::ActiveRecordExtension::ClassMethods
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer "filterrific.action_view_extension" do |app|
|
19
|
+
require 'filterrific/action_view_extension'
|
20
|
+
class ::ActionView::Base
|
21
|
+
include Filterrific::ActionViewExtension
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -1,21 +1,23 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
|
1
3
|
module Filterrific
|
2
4
|
|
3
|
-
# FilterParamSet is a container to store FilterParams
|
5
|
+
# FilterParamSet is a container to store FilterParams
|
4
6
|
class ParamSet
|
5
7
|
|
6
8
|
attr_accessor :resource_class
|
7
9
|
|
8
|
-
def initialize(
|
10
|
+
def initialize(a_resource_class, filterrific_params = {})
|
9
11
|
|
10
|
-
self.resource_class =
|
12
|
+
self.resource_class = a_resource_class
|
11
13
|
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# by the user, then it will be overriden by
|
14
|
+
# Use either passed in filterrific_params or resource class' default_settings.
|
15
|
+
# Don't merge the hashes. This causes trouble if an option is set to nil
|
16
|
+
# by the user, then it will be overriden by default_settings.
|
15
17
|
# You might wonder "what if I want to change only one thing from the defaults?"
|
16
18
|
# Persistence, baby. By the time you submit changes to one dimension, all the others
|
17
19
|
# will be already initialized with the defaults.
|
18
|
-
filterrific_params = resource_class.
|
20
|
+
filterrific_params = resource_class.filterrific_default_settings if filterrific_params.blank?
|
19
21
|
|
20
22
|
# force all keys to strings
|
21
23
|
filterrific_params.stringify_keys!
|
@@ -27,47 +29,54 @@ module Filterrific
|
|
27
29
|
# evaulate Procs
|
28
30
|
filterrific_params[key] = val.call
|
29
31
|
when val.is_a?(Array)
|
30
|
-
# type cast integers
|
32
|
+
# type cast integers in the array
|
31
33
|
filterrific_params[key] = filterrific_params[key].map { |e| e =~ /^\d+$/ ? e.to_i : e }
|
32
34
|
when val =~ /^\d+$/
|
33
|
-
# type cast
|
35
|
+
# type cast integer
|
34
36
|
filterrific_params[key] = filterrific_params[key].to_i
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
# Define attr_accessor for each
|
40
|
+
# Define attr_accessor for each filterrific_filter_name
|
39
41
|
# on Filterrific::ParamSet instance and assign values from options
|
40
|
-
resource_class.
|
41
|
-
self.class.send(:attr_accessor,
|
42
|
-
v = filterrific_params[
|
43
|
-
self.send("#{
|
42
|
+
resource_class.filterrific_filter_names.each do |filter_name|
|
43
|
+
self.class.send(:attr_accessor, filter_name)
|
44
|
+
v = filterrific_params[filter_name]
|
45
|
+
self.send("#{ filter_name }=", v) if v.present?
|
44
46
|
end
|
45
47
|
|
46
48
|
end
|
47
49
|
|
48
|
-
# Returns
|
50
|
+
# Returns Filterrific::ParamSet as hash (used for URL params and serialization)
|
49
51
|
def to_hash
|
50
52
|
{}.tap { |h|
|
51
|
-
resource_class.
|
52
|
-
param_value = self.send(
|
53
|
+
resource_class.filterrific_filter_names.each do |filter_name|
|
54
|
+
param_value = self.send(filter_name)
|
53
55
|
case
|
54
56
|
when param_value.blank?
|
55
57
|
# do nothing
|
56
58
|
when param_value.is_a?(Proc)
|
57
59
|
# evaluate Proc so it can be serialized
|
58
|
-
h[
|
60
|
+
h[filter_name] = param_value.call
|
59
61
|
else
|
60
|
-
h[
|
62
|
+
h[filter_name] = param_value
|
61
63
|
end
|
62
64
|
end
|
63
65
|
}
|
64
66
|
end
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
-
resource_class.default_filterrific_params != to_hash
|
68
|
+
def to_json
|
69
|
+
to_hash.to_json
|
69
70
|
end
|
70
71
|
|
72
|
+
# Returns true if this Filterrific::ParamSet is not the model's default.
|
73
|
+
# TODO: this doesn't work for procs. I need to evaluate the
|
74
|
+
# filterrific_default_settings before comparing them to to_hash.
|
75
|
+
#
|
76
|
+
# def customized?
|
77
|
+
# resource_class.filterrific_default_settings != to_hash
|
78
|
+
# end
|
79
|
+
|
71
80
|
end
|
72
81
|
|
73
82
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'filterrific/action_view_extension'
|
3
|
+
|
4
|
+
class ViewContext
|
5
|
+
|
6
|
+
include Filterrific::ActionViewExtension
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Filterrific::ActionViewExtension do
|
11
|
+
|
12
|
+
it "renders filterrific spinner" do
|
13
|
+
ViewContext.new.should respond_to(:render_filterrific_spinner)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record'
|
3
|
+
require 'filterrific/active_record_extension'
|
4
|
+
::ActiveRecord::Base.extend Filterrific::ActiveRecordExtension::ClassMethods
|
5
|
+
|
6
|
+
# Container for test data
|
7
|
+
class TestData
|
8
|
+
|
9
|
+
def self.filterrific_filter_names
|
10
|
+
%w[sorted_by search_query with_country_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.filterrific_default_settings
|
14
|
+
{ 'sorted_by' => 'name_asc' }
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Filterrific::ActiveRecordExtension do
|
20
|
+
|
21
|
+
let(:filterrific_class){
|
22
|
+
Class.new(ActiveRecord::Base) do
|
23
|
+
filterrific(
|
24
|
+
:filter_names => TestData.filterrific_filter_names,
|
25
|
+
:default_settings => TestData.filterrific_default_settings
|
26
|
+
)
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
describe "Class method extensions" do
|
31
|
+
|
32
|
+
it "adds a 'filterrific' class method" do
|
33
|
+
filterrific_class.should respond_to(:filterrific)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "adds a 'filterrific_find' class method" do
|
37
|
+
filterrific_class.should respond_to(:filterrific_find)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "Filterrific initialization" do
|
43
|
+
|
44
|
+
it "initializes filterrific_filter_names" do
|
45
|
+
filterrific_class.filterrific_filter_names.should == TestData.filterrific_filter_names
|
46
|
+
end
|
47
|
+
|
48
|
+
it "initializes filterrific_default_settings" do
|
49
|
+
filterrific_class.filterrific_default_settings.should == TestData.filterrific_default_settings
|
50
|
+
end
|
51
|
+
|
52
|
+
it "raises when no filter_names are given" do
|
53
|
+
expect {
|
54
|
+
Class.new(ActiveRecord::Base) do
|
55
|
+
filterrific(
|
56
|
+
:filter_names => []
|
57
|
+
)
|
58
|
+
end
|
59
|
+
}.to raise_error(ArgumentError)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "raises when default_settings contains keys that are not in filter_names" do
|
63
|
+
expect {
|
64
|
+
Class.new(ActiveRecord::Base) do
|
65
|
+
filterrific(
|
66
|
+
:filter_names => [:one, :two],
|
67
|
+
:default_settings => { :three => '' }
|
68
|
+
)
|
69
|
+
end
|
70
|
+
}.to raise_error(ArgumentError)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "filterrific_find" do
|
76
|
+
|
77
|
+
it "raises when given invalid params" do
|
78
|
+
expect {
|
79
|
+
filterrific_class.filterrific_find('an invalid argument')
|
80
|
+
}.to raise_error(ArgumentError)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'filterrific/param_set'
|
3
|
+
|
4
|
+
# Container for test data
|
5
|
+
class TestData
|
6
|
+
|
7
|
+
def self.filterrific_filter_names
|
8
|
+
%w[
|
9
|
+
filter_proc
|
10
|
+
filter_array_int
|
11
|
+
filter_array_string
|
12
|
+
filter_int
|
13
|
+
filter_string
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.filterrific_default_settings
|
18
|
+
{ 'filter_int' => 42 }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.filterrific_params
|
22
|
+
{
|
23
|
+
'filter_proc' => lambda { 1 + 1 },
|
24
|
+
'filter_array_int' => %w[1 2 3],
|
25
|
+
'filter_array_string' => %w[one two three],
|
26
|
+
'filter_int' => '42',
|
27
|
+
'filter_string' => 'forty-two'
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.filterrific_params_after_sanitizing
|
32
|
+
{
|
33
|
+
'filter_proc' => 2,
|
34
|
+
'filter_array_int' => [1, 2, 3],
|
35
|
+
'filter_array_string' => %w[one two three],
|
36
|
+
'filter_int' => 42,
|
37
|
+
'filter_string' => 'forty-two'
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Simulates a class that would include the filterrific directive
|
44
|
+
class ResourceClass
|
45
|
+
|
46
|
+
def self.filterrific_default_settings
|
47
|
+
TestData.filterrific_default_settings
|
48
|
+
end
|
49
|
+
def self.filterrific_filter_names
|
50
|
+
TestData.filterrific_filter_names
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe Filterrific::ParamSet do
|
56
|
+
|
57
|
+
let(:filterrific_param_set){
|
58
|
+
Filterrific::ParamSet.new(ResourceClass, TestData.filterrific_params)
|
59
|
+
}
|
60
|
+
|
61
|
+
describe "initialization" do
|
62
|
+
|
63
|
+
it "assigns resource class" do
|
64
|
+
filterrific_param_set.resource_class.should == ResourceClass
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "dynamic filter_name attr_accessors" do
|
68
|
+
|
69
|
+
TestData.filterrific_filter_names.each do |filter_name|
|
70
|
+
|
71
|
+
it "defines a getter for '#{ filter_name }'" do
|
72
|
+
filterrific_param_set.should respond_to(filter_name)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "defines a setter for '#{ filter_name }'" do
|
76
|
+
filterrific_param_set.should respond_to("#{ filter_name }=")
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
TestData.filterrific_params.keys.each do |key|
|
82
|
+
|
83
|
+
it "assigns sanitized param to '#{ key }' attr" do
|
84
|
+
filterrific_param_set.send(key).should == TestData.filterrific_params_after_sanitizing[key]
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "to_hash" do
|
94
|
+
|
95
|
+
it "returns all filterrific_params as hash" do
|
96
|
+
filterrific_param_set.to_hash.should == TestData.filterrific_params_after_sanitizing
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "to_json" do
|
102
|
+
|
103
|
+
it "returns all filterrific_params as json string" do
|
104
|
+
filterrific_param_set.to_json.should == TestData.filterrific_params_after_sanitizing.to_json
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|