filterrific 1.0.1 → 1.1.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.
- 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
|