filterrific 0.2.0 → 1.0.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 CHANGED
@@ -1,3 +1,8 @@
1
+ # 1.0.0
2
+
3
+ * Support for Rails 3.1
4
+ * new model api
5
+
1
6
  ## 0.1.0, released 2010-08-01
2
7
 
3
8
  * Replicate functionality of Rails 2.3 version
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Jo Hund
1
+ Copyright (c) 2010-2011 Jo Hund
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 1.0.0
@@ -0,0 +1,140 @@
1
+ Conventions and API for filterrific scopes:
2
+
3
+ Philosophy:
4
+
5
+ auto generate simple scopes. Any customization is done by overriding the scope. Give
6
+ lots of examples and annotated code.
7
+
8
+ List of exposed scopes. On first request, load and cache them so that all meta-code has had a chance
9
+ to run (e.g. to dynamically generate scopes).
10
+
11
+ Only filters defined in filterrific will be exposed via URL. Other scopes will not be accessible
12
+
13
+
14
+
15
+ CONFIG
16
+ ===============================================================================
17
+
18
+ * persist_in_session
19
+ * default_label_method (for view helper selects, radio buttons, checkboxes), default => :name
20
+
21
+
22
+
23
+
24
+
25
+ Examples
26
+
27
+
28
+ CVIMS:
29
+ * with_publishing_status: custom, looks at state as well as date
30
+ * search_query
31
+ * sorted_by: extracts direction, applies lower(...) to string columns
32
+ * with_tags IN
33
+ * authored_by belongs_to, { :author_id => [*author_ids] }
34
+
35
+
36
+ PTS:
37
+ * has_list_options :defaults => {
38
+ :publication_date_min => lambda { Date.new(Date.today.year, 1, 1).to_s(:calendar_display_area) },
39
+ :publication_date_max => lambda { Date.new(Date.today.year + 3, 12, 31).to_s(:calendar_display_area) },
40
+ :sorted_by => 'publication_date_asc',
41
+ :with_language => Language.all.map(&:id),
42
+ :with_programme => Programme.all.map(&:id),
43
+ :with_publication_status => [1, 2, 5, 7],
44
+ :with_publication_type => PublicationType.all.map(&:id) - [PublicationType.find_partner.nil_or(:id)],
45
+ :with_sales_item_status => %w[sales_item non_sales_item],
46
+ :with_commercial_title_status => %w[commercial_title non_commercial_title]
47
+ }
48
+ * interesting method: condition_publication_date_for_named_scope (allows pseudo procs for dates).
49
+ helpful because procs cannot be marshalled
50
+ * # have to use :include instead of :joins for :roles. If I use :joins, it does an inner join and
51
+ # returns duplicates for publications. Same for :with_role_type scope.
52
+ # roles is a habtm relation with publication.
53
+ named_scope :with_person, lambda{ |person_ids|
54
+ {
55
+ :conditions => ["roles.person_id IN (?)", [*person_ids].map(&:to_i)],
56
+ :include => :roles
57
+ }
58
+ }
59
+ * named_scope :with_commercial_title_status, lambda{ |statuses|
60
+ v = ['1 = 2'] # never true, used if no checkbox is checked ("0")
61
+ v << 'publications.service_category_id = 1' if statuses.include?("commercial_title")
62
+ v << 'publications.service_category_id != 1' if statuses.include?("non_commercial_title")
63
+ { :conditions => v.join(' OR ') }
64
+ }
65
+ * named_scope :sorted_by, lambda { |sort_option|
66
+ # every sorting should have a set of secondary sort keys. We append those for each case where applicable.
67
+ secondary_sort_keys = "publications.publication_date asc, lower(publications.short_title) asc, publications.id asc"
68
+ direction = (sort_option =~ /desc$/) ? 'desc' : 'asc'
69
+ case sort_option.to_s
70
+ when /^extent_/
71
+ { :order => "publications.extent #{direction}" }
72
+ when /^language_/
73
+ { :order => "lower(languages.name) #{direction}, #{secondary_sort_keys}",
74
+ :joins => :language }
75
+ when /^PM_/
76
+ { :order => "lower(publications.project_manager_name) #{direction}, #{secondary_sort_keys}" }
77
+ when /^programme_/
78
+ { :order => "lower(programmes.name) #{direction}, #{secondary_sort_keys}",
79
+ :joins => :programme }
80
+ else
81
+ raise(ArgumentError, "Invalid sort option: #{sort_option.inspect}")
82
+ end
83
+ }
84
+
85
+
86
+
87
+ TIRO
88
+ * filterrific :defaults => {
89
+ :sorted_by => 'most_recent',
90
+ :on_or_after => lambda { Time.zone.now.beginning_of_month.to_s(:date) },
91
+ :billable => true
92
+ }
93
+ * scope :billable, lambda{ |yes_or_no|
94
+ case yes_or_no
95
+ when "yes", true
96
+ where('activities.is_billable = ?', true)
97
+ when "no", false
98
+ where('activities.is_billable = ?', false)
99
+ else
100
+ # no setting, don't return any conditions
101
+ end
102
+ }
103
+ * scope :for_person, lambda { |person| where("projects.person_id = ?", person.id).joins(:project) }
104
+ * scope :for_date_range, lambda{ |dates|
105
+ where('activities.date <= ? AND activities.date >= ?', dates.max, dates.min)
106
+ }
107
+ * scope :on_or_after, lambda{ |date|
108
+ d = Date.parse(date) if date.is_a?(String)
109
+ where('activities.date >= ? ', d)
110
+ }
111
+
112
+
113
+
114
+
115
+ CANDO
116
+ * saved searches
117
+ * named_scope :search_query, lambda{ |query|
118
+ # Matches using LIKE. LIKE is case INsensitive with MySQL (Development),
119
+ # however it is case sensitive with PostGreSQL (Heroku)
120
+ # To make it work in both worlds, I force everything to lower case.
121
+ return {} unless query.is_a?(String)
122
+ # condition query string, parse into individual keywords
123
+ terms = query.downcase.split(/\s+/)
124
+ # replace "*" with "%" for wildcard searches
125
+ terms = terms.map { |e| e.gsub('*', '%') }
126
+ {
127
+ :conditions => [
128
+ terms.map { |term|
129
+ "lower(conference_participations._first_name) LIKE ? OR lower(conference_participations._last_name) LIKE ?"
130
+ }.join(' AND '),
131
+ *(terms.map { |e| e.downcase } * 2)
132
+ ]
133
+ }
134
+ }
135
+
136
+
137
+
138
+ STRATADOCS
139
+
140
+ * list starts with self as AR proxy
@@ -0,0 +1,27 @@
1
+ CONTROLLER
2
+ ==========
3
+
4
+ There are two methods you use in a controller:
5
+ * filterrific_init to initialize the Filterrific object. It is stored in the @filterrific ivar. This
6
+ method first initializes the filter params:
7
+ params[param_name] || session[session_key] || Model.filterrific_defaults
8
+ Then it persists the filter_params in the session, using session_key.
9
+ * Model.filterrific_find to populate your ActiveRecord collection based on filterrific params.
10
+ filterrific_find is a composable ActiveRecord relation. That means you can chain it with other
11
+ relations like scopes, pagination, etc.
12
+
13
+ Example controller code:
14
+
15
+ def index
16
+ @filterrific = filterrific_init(Publication, options={})
17
+ Options:
18
+ * :persist_in_session => true OR "publications_list" # if true, builds session_key from
19
+ controller-action
20
+ * :debug => false # if true, prints out debug info. This also exists in view helper. Maybe one to
21
+ logger/STDOUT, the other to view?
22
+ * :param_prefix => "filterrific" # the param prefix used to shuttle params between view and controller.
23
+
24
+ @publications = Publication.filterrific_find(@filterrific).paginate....
25
+ @publications = current_user.publications.filterrific_find(@filterrific).paginate....
26
+ ...
27
+ end
@@ -0,0 +1,485 @@
1
+ filterrific do
2
+ filter :with_project
3
+ sort ...
4
+ search ...
5
+ config :prefix, 'lala'
6
+ config.debug = true
7
+ end
8
+
9
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
10
+
11
+ # TODO: find out why in PTS publications filter, some hidden fields are set to nil and others to "0".
12
+ # If I change it, filter stops working
13
+
14
+
15
+ filterrific do
16
+
17
+ # Creates custom scope "filterrific_person". Finds all records that have one of the given values
18
+ # in :person_id column
19
+ # Accepts: Person.new, 17, [Person.new, Person.new], [12, 13, 14], [12, 13, Person.new]
20
+ # scope :filterrific_person, lambda { |persons|
21
+ # sanitize: convert all entries to an id (integer), convert to array, do nothing if persons.empty?
22
+ # foreign_key: inspect association to get foreign key
23
+ # where(:person_id => persons)
24
+ # }
25
+ filter :belongs_to => :person
26
+
27
+ # Creates custom scope "filterrific_is_active". Finds all records that have one of the given
28
+ # values for :is_active column
29
+ # Accepts: "true", true, [true, false], [true, 13, false]
30
+ # scope :filterrific_is_active, lambda { |values|
31
+ # sanitize: convert all entries to column type, convert to array, do nothing if values.empty?
32
+ # where(:is_active => values)
33
+ # }
34
+ filter :column => :is_active, :default => true
35
+
36
+ # Creates scopes for date/datetime columns.
37
+ # Possible conditions: :after, :before, :on_or_after, :on_or_before
38
+ # scope :filterrific_created_at_on_or_after, lambda { |datetime|
39
+ # datetime = sanitize datetime
40
+ # return nil if datetime.blank
41
+ # table_name = get tablename from class
42
+ # where(['tablename.created_at >= ?', datetime])
43
+ # }
44
+ filter :column => :created_at, :condition => :on_or_after, :default => Proc.new { 2.years.ago.beginning_of_year }
45
+ filter :column => :last_login_on, :condition => :before
46
+
47
+ # Creates custom scope "filterrific_some_complex_scope". Uses scope :some_complex_scope
48
+ # scope :filterrific_some_complex_scope, lambda { |*args|
49
+ # some_complex_scope(args)
50
+ # }
51
+ filter :scope => :some_complex_scope
52
+
53
+ # Available options for filter:
54
+ #
55
+ # Options for filter type:
56
+ # :belongs_to: to filter on a belongs_to association
57
+ # :column: to filter on a column
58
+ # :scope: to delegate filtering to a scope that is already defined on the class
59
+ #
60
+ # General options:
61
+ # :default: to set the default, can be static value or Proc.new
62
+ # :name: to change the name for a given filter. The name will still be prefixed with filterrific_prefix
63
+ #
64
+ # Possible conditions for :column filters:
65
+ # * in: contained in set, IN (This is the default condition)
66
+ # * comparisons: :after, :before, :on_or_after, :on_or_before, >, <, >=, <=
67
+ # * equals: exactly one, =
68
+ # * like: LIKE
69
+ # * between: BETWEEN a AND b
70
+
71
+ # Creates custom scope named "filterrific_sort"
72
+ sort(
73
+ :sort_keys => [
74
+ { :key => :newest_first, :label => "Newest first", :sql => "created_at DESC" },
75
+ { :key => :alphabetically, :label => "Name (a-z)", :sql => "name ASC" },
76
+ ],
77
+ :default => :newest_first,
78
+ :secondary_sorting => "name ASC" # will be appended to every sort as secondary sort key
79
+ )
80
+
81
+ # Creates custom scope named "filterrific_search"
82
+ search(
83
+ :columns => [:name, :email, 'provinces.name'], # default: all string columns
84
+ :joins => :province,
85
+ :case_sensitive => false, # adds downcase parts to term_processor and SQL query if false
86
+ :term_splitter => /\s+/,
87
+ :wild_card_processor => Proc.new({ |e| "%#{ e }%" }) OR Proc.new({ |e| e.gsub('*', '%') }),
88
+ :default => "some query",
89
+ :param_name => "search" # somebody might want to set it to "q"
90
+ )
91
+ # Alternative search: delegate to pg_search for fulltext search on Postgres
92
+
93
+ # If true, prints auto generated and used scopes, current filterrific params, whether all DB
94
+ # columns have indices
95
+ config :debug => true # should there be different log levels? option to set output (log, puts)
96
+ # Prefix is applied to scopes that are available to filterrific. Note that this is different
97
+ # from the param_prefix that can be set in the controller config.
98
+ config :scope_prefix => "filterrific_" # default, somebody might want to shorten or remove prefix
99
+
100
+ end
101
+
102
+ scope :some_complex_scope, lambda { |a_value| ... }
103
+
104
+
105
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
106
+
107
+
108
+ filterrific do
109
+
110
+ # Creates custom scope "filterrific_person". Finds all records that have one of the given values
111
+ # in :person_ids column
112
+ # Q: Could I skip the :assciation => true piece? Filterrific could figure this out automatically
113
+ filter :person, :association => true # instead of true, could be an association name, e.g. :user
114
+ filter :association => :person # alternative syntax A
115
+ association :person # alternative syntax B
116
+ # Creates custom scope "filterrific_active_only". Finds all records that have one of the given
117
+ # values for :is_active column
118
+ filter :active_only, :column => :is_active || :some_scope_name, :default => true
119
+ filter :column => :is_active, :default => true # alternative syntax A
120
+ column :is_active, :default => true # alternative syntax B
121
+ # Creates custom scope "filterrific_created_after". Finds all records that have created_at after
122
+ # the given DateTime
123
+ filter :created_after, :column => :created_at, :default => Proc.new { 2.years.ago.beginning_of_year }
124
+ # Creates custom scope "filterrific_complicated_scope". Delegates to an already existing scope
125
+ # named :complicated_scope_name
126
+ # Q: How does it know the params expected by :complicated_scope_name?
127
+ # A: scope :filterrific_complicated_scope_name, lambda { |*attrs| complicated_scope_name(attrs) }
128
+ filter :complicated_scope, :use_scope => :complicated_scope_name
129
+ filter :scope => :complicated_scope_name # alternative syntax A
130
+ scope :complicated_scope_name # alternative syntax B
131
+
132
+ # Creates custom scope named "filterrific_sort"
133
+ sort(
134
+ :sort_keys => [
135
+ { :key => :newest_first, :label => "Newest first", :sql => "created_at DESC" },
136
+ { :key => :alphabetically, :label => "Name (a-z)", :sql => "name ASC" },
137
+ ],
138
+ :default => :newest_first,
139
+ :secondary_sorting => "name ASC" # will be appended to every sort as secondary sort key
140
+ )
141
+
142
+ # Creates custom scope named "filterrific_search"
143
+ search(
144
+ :columns => [:name, :email, 'provinces.name'], # default: all string columns
145
+ :joins => :province,
146
+ :case_sensitive => false, # adds downcase parts to term_processor and SQL query if false
147
+ :term_splitter => /\s+/,
148
+ :wild_card_processor => Proc.new({ |e| "%#{ e }%" }) OR Proc.new({ |e| e.gsub('*', '%') }),
149
+ :default => "some query",
150
+ :param_name => "search" # somebody might want to set it to "q"
151
+ )
152
+
153
+ # If true, prints auto generated and used scopes, current filterrific params, whether all DB
154
+ # columns have indices
155
+ config :debug => true # should there be different log levels? option to set output (log, puts)
156
+ config :param_prefix => "filterrific_" # default, somebody might want to shorten or remove prefix
157
+
158
+ auto generate from association
159
+ auto generate from column
160
+ delegate to existing scope
161
+ sort auto or refer
162
+ search auto or refer
163
+
164
+ end
165
+
166
+ scope :complicated_scope_name, lambda { |...| }
167
+
168
+
169
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
170
+
171
+ filterrific do
172
+
173
+ filter :publication_date_min,
174
+ :use_scope => :publication_date_min,
175
+ :default => lambda { Date.new(Date.today.year, 1, 1).to_s(:calendar_display_area) }
176
+ # need to handle date pre-processing (condition_publication_date_for_named_scope)
177
+ filter :publication_date_max,
178
+ :column => :publication_date,
179
+ :condition => :on_or_before,
180
+ :default => lambda { Date.new(Date.today.year + 3, 12, 31).to_s(:calendar_display_area) }
181
+ filter :group, :belongs_to => true, :default => Language.all.map(&:id)
182
+ filter :language, :belongs_to => true
183
+ filter :owner, :belongs_to => true # could find the association automatically. Don't need belongs_to here
184
+ filter :person, :use_scope => :with_person
185
+ filter :programme, :belongs_to => true, :default => Programme.all.map(&:id)
186
+ filter :publication_status, :belongs_to => true, :default => [1, 2, 5, 7]
187
+ filter :publication_type,
188
+ :belongs_to => true,
189
+ :default => PublicationType.all.map(&:id) - [PublicationType.find_partner.nil_or(:id)]
190
+ filter :role_type, :use_scope => :with_role_type
191
+ filter :department, :use_scope => :for_department
192
+
193
+ # or just referencing stuff that already is declared in the class
194
+ belongs_to :group # creates scope 'filterrific_with_group'
195
+ belongs_to :language, :default => Language.all.map(&:id)
196
+ belongs_to :owner, :default => Programme.all.map(&:id)
197
+ belongs_to :programme
198
+ belongs_to :publication_status, :default => [1, 2, 5, 7], :param_name => :status
199
+ belongs_to :publication_type, :default => PublicationType.all.map(&:id) - [PublicationType.find_partner.nil_or(:id)]
200
+
201
+ scope :publication_date_min, :default => lambda { Date.new(Date.today.year, 1, 1).to_s(:calendar_display_area) }
202
+ scope :publication_date_max, :default => lambda { Date.new(Date.today.year + 3, 12, 31).to_s(:calendar_display_area) }
203
+ scope :with_person
204
+ scope :with_role_type
205
+ scope :for_department
206
+ scope :with_sales_item_status, :default => %w[sales_item non_sales_item],
207
+ scope :with_commercial_title_status, :default => %w[commercial_title non_commercial_title]
208
+
209
+ column: checks for inclusion
210
+
211
+ flag: the sales item checkboxes in PTS
212
+
213
+ search :columns => [:short_title], :param_name => "search_short_title"
214
+
215
+ # Two modes: auto generate or manual. If auto, can generate two directions. Manual will always
216
+ # generate one direction only.
217
+ # Distinction: :order-option given? ? :manual : :auto
218
+ #
219
+ # common options: :key, :label (default: key.to_s.titleize), :apply_secondary_sorting (default: true)
220
+ # auto only options: :directions (default: [:asc, :desc]),
221
+ # manual only options: :order, :joins
222
+ sort(
223
+ [
224
+ { :key => :newest_first, :label => "Newest first", :order => "publications.created_at DESC" },
225
+ { :key => :alphabetically, :label => "Name (a-z)", :order => "users.name ASC", :joins => :users }
226
+ # creates sort_options extent_asc and extent_desc. Label is defined here on each sort_key.
227
+ # Would be cumbersome in view.
228
+ # Creates sort: order("publications.extent ASC, <secondary sort>"), label: "Extent Asc" and
229
+ # order("publications.extent DESC, <secondary sort>"), label: "Extent Desc"
230
+ { :key => :extent, :directions => [:asc, :desc] }, # [:asc, :desc], :asc, :desc, [:asc]
231
+ { :key => :id },
232
+ { :key => :owner_id },
233
+ { :key => :paf_status_id },
234
+ { :key => :project_manager_name, :label => "PM" },
235
+ { :key => :publication_date },
236
+ { :key => :publication_type },
237
+ { :key => :sales_item },
238
+ { :key => :short_title },
239
+ { :key => :updated_at, :apply_secondary_sorting => false },
240
+ { :key => :programme_asc, :order => "lower(programmes.name) ASC", :joins => :programme },
241
+ { :key => :language_asc, :order => "lower(languages.name) ASC", :joins => :language },
242
+ { :key => :status, :order => "lower(publication_statuses.name) ASC", :joins => :publication_status }
243
+ ],
244
+ :default => :newest_first,
245
+ :param_name => :sort,
246
+ :case_sensitive => false # this uses the lower() SQL modifier around any string columns for auto generated search options
247
+ :secondary_sorting => {
248
+ :order => "publications.publication_date asc, lower(publications.short_title) asc, publications.id asc",
249
+ :joins => :paf_status
250
+ }
251
+ )
252
+
253
+ end
254
+
255
+
256
+ # have to use :include instead of :joins for :roles. If I use :joins, it does an inner join and
257
+ # returns duplicates for publications. Same for :with_role_type scope.
258
+ # roles is a habtm relation with publication.
259
+ named_scope :with_person, lambda{ |person_ids|
260
+ {
261
+ :conditions => ["roles.person_id IN (?)", [*person_ids].map(&:to_i)],
262
+ :include => :roles
263
+ }
264
+ }
265
+ named_scope :with_role_type, lambda{ |role_type_ids|
266
+ {
267
+ :conditions => ["roles.role_type_id IN (?)", [*role_type_ids].map(&:to_i)],
268
+ :include => :roles
269
+ }
270
+ }
271
+ named_scope :for_department, lambda{ |department_ids|
272
+ phase_boundary_event_types = Department.find(
273
+ [*department_ids].map(&:to_i)
274
+ ).map(&:phase_boundary_event_types).flatten
275
+ { :conditions => ["events.event_type_id IN (?)", phase_boundary_event_types.map(&:id)], :joins => :events }
276
+ }
277
+ named_scope :with_sales_item_status, lambda{ |statuses|
278
+ v = ['1 = 2'] # never true, used if no checkbox is checked ("0")
279
+ v << 'publications.sales_item = 1' if statuses.include?("sales_item")
280
+ v << 'publications.sales_item != 1' if statuses.include?("non_sales_item")
281
+ { :conditions => v.join(' OR ') }
282
+ }
283
+ named_scope :with_commercial_title_status, lambda{ |statuses|
284
+ v = ['1 = 2'] # never true, used if no checkbox is checked ("0")
285
+ v << 'publications.service_category_id = 1' if statuses.include?("commercial_title")
286
+ v << 'publications.service_category_id != 1' if statuses.include?("non_commercial_title")
287
+ { :conditions => v.join(' OR ') }
288
+ }
289
+
290
+
291
+
292
+
293
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+
304
+
305
+
306
+
307
+ # Defines a filter available to Filterrific
308
+ #
309
+ # Filters are used to retrieve a subset of all records available, based on a given criterion.
310
+ # Filterrific uses Rails' scopes for this purpose. For each filter specified, Filterrific will
311
+ # create a new scope with the name "filterrific_#{ filter_key }".
312
+ #
313
+ # Filter type option
314
+ #
315
+ # This option tells Filterrific about the kind of filter required. If not given, F tries to figure
316
+ # out the filter type from the filter_key, in the following order:
317
+ #
318
+ # 1) ActiveRecord association on the model with the same name as filter_key
319
+ # 2) Column in the model's table with the same name as filter_key
320
+ # 3) Existing scope on the model with the same name as filter_key
321
+ #
322
+ # Accepts:
323
+ #
324
+ # * :association(better belongs_to???) - this filter acts on an ActiveRecord association, typically a belongs_to.
325
+ # Expects: true (F uses the filter_key as association name) or the name of the association as
326
+ # Symbol or String.
327
+ # * :column - this filter acts on a column on this model's table. Filterrific looks at the column
328
+ # type to determine what kind of scope is required. See below for column_type specific options.
329
+ # Expects: true (F uses the filter_key as column_name) or the name of the column as Symbol or String.
330
+ # * :use_scope - accepts Symbod/String to delegate to an existing scope that is already defined on the
331
+ # model. Alternatively accepts a Proc to define a scope within filterrific. Use the same syntax
332
+ # as for Rails scopes.
333
+ # Expects: true (F uses the filter_key as scope_name) or the name of the scope as Symbol or String.
334
+ #
335
+ # General options
336
+ #
337
+ # These options are applicable to all filter types
338
+ #
339
+ # * :default - the default value for this filter. Used on first call and after reset_filterrific.
340
+ # Set this on any filter that has a default setting.
341
+ add param name?
342
+ #
343
+ # SQL Options
344
+ #
345
+ # These options are applicable for filters that generate SQL
346
+ #
347
+ # * :joins => tables to join
348
+ # * :includes => tables to include
349
+ #
350
+ # Date filter options
351
+ #
352
+ # These options are applicable to a filter of type :column where the column is of type date.
353
+ #
354
+ # * :condition - Expects one of :before, :after, :between, :on_or_after, :on_or_before, :at_or_after, :at_or_before
355
+ #
356
+ # @param [Symbol, String] filter_key the key to be used for this filter
357
+ # @param [Hash] opts the options to specify this filter
358
+ # @return ?
359
+ #
360
+ filter(filter_key, filter_type, opts = {})
361
+ end
362
+
363
+
364
+ # Defines sorting for Filterrific
365
+ #
366
+ # Sort is a special type of filter. It is used to allow a user to choose the sorting of a collection.
367
+ #
368
+ # Uses sort_key as internal identifier and also as name for the form param. Creates a scope named
369
+ # "filterrific_#{ sort_key }".
370
+ #
371
+ # Options if using Filterrific internal sort
372
+ #
373
+ # * :sort_keys - A hash for each sort key with the following options:
374
+ # ** :key - internal name for this sort key. If no :sql given, sorts by column with this name
375
+ # ** :label - label for this sort key, used in views. ?? Shouldn't this be in view helpers?'
376
+ # ** :sql - a SQL fragment used as is to specify sorting
377
+ # ** :joins - joins to be performed for sorting
378
+ # * :secondary_sorting - sort_key that will be appended to every sort as secondary sort key
379
+ # * :default - the :key of the default sort order
380
+ # * :joins - joins to be added to every sort operation (might be required for secondary sorting)
381
+ #
382
+ # Options if using existing scope for sort
383
+ #
384
+ # * :scope - the name of an existing scope to be used for sort
385
+ # * :default - the :key of the default sort order
386
+ #
387
+ # @param [Array<Hash>] sort_options the sort options to be offered
388
+ # @param [Hash] opts the options to specify sorting
389
+ # @return ?
390
+ #
391
+ def sort(sort_options, opts = {})
392
+ end
393
+
394
+
395
+ # Defines search for Filterrific.
396
+ # Search is a special type of filter. It is used to retrieve records from the database that match
397
+ # a given search string. This is typically not as powerful as an indexed search engine, however
398
+ # it can be sufficient in a large number of use cases and is a lot simpler to set up than an index
399
+ # search server like Solr or Lucene (verify search server list!!!).
400
+ #
401
+ # Uses search_key as internal identifier and also as name for the form param. Creates a scope named
402
+ # "filterrific_#{ search_key }".
403
+ #
404
+ # Options if using Filterrific internal search
405
+ #
406
+ # * :columns => [:name, :email, 'provinces.name'], # default: all string columns
407
+ # * :joins => :province
408
+ # * :case_sensitive => false, (adds downcase parts to term_processor and SQL query if false)
409
+ # * :term_splitter => /\s+/
410
+ # * :wild_card_processor => Proc.new({ |e| "%#{ e }%" }) OR Proc.new({ |e| e.gsub('*', '%') })
411
+ # * :default - the value for the default search string. Default: nil
412
+ #
413
+ # Options if using existing scope for search
414
+ #
415
+ # * :scope => :fancy_search # name of an existing scope to be used for search
416
+ # * :default - the value for the default search string. Default: nil
417
+ #
418
+ # @param [Symbol, String] search_key the key to be used for searching
419
+ # @param [Hash] opts the options to specify searching
420
+ # @return ?
421
+ #
422
+ # * CVIMS Content looks like a good and general solution
423
+ #
424
+ def search(search_key, opts = {})
425
+ end
426
+
427
+
428
+ # Configures one or more aspects of Filterrific.
429
+ #
430
+ # Available Config options
431
+ #
432
+ # * :debug => true or output type (log, stdout)
433
+ # * :sql_type => :mysql (or :postgresql), possible oracle? Determines how search is handled (regex
434
+ # vs. like), and any other differences. Could we use Rails' DB connectors for this?
435
+ #
436
+ # @param [Hash] opts the config options
437
+ # @return ?
438
+ #
439
+ def config(opts = {})
440
+ end
441
+
442
+
443
+
444
+
445
+ Auto generated scopes for author:
446
+
447
+ * belongs_to: scope :filterrific_with_author_ids, lambda { |author_ids| where({ :author_id => *[author_ids] }
448
+ * has_many: scope exists?
449
+ * collection
450
+ * attribute [String]
451
+ * attribute [Numeric]
452
+ * attribute [Date, DateTime]
453
+ * attribute [Boolean,TinyInt]
454
+ * attribute [Text]
455
+ *
456
+
457
+ Each type uses a kind of Filterrific::Matcher:
458
+
459
+ * search
460
+ * date_time
461
+ * foreign_key
462
+
463
+
464
+
465
+ MODEL
466
+ =====
467
+
468
+ filterrific do
469
+
470
+ associations do
471
+ person # creates a scope for filterrific
472
+ state, :default => [1,2,3,4], :type => :select, :multiple => 4
473
+ end
474
+
475
+ scopes do
476
+ active_only, :default => true # refers to already defined scope
477
+ search, :as => "q" (optional param name override)
478
+ sorted_by, :default => "created_at_desc"
479
+ end
480
+
481
+ config :debug => true # prints auto generated and used scopes, current filterrific params, whether all DB columns have indices
482
+ config ... can't think of any other config options yet.
483
+ end
484
+
485
+ scope :search, ...
@@ -0,0 +1,49 @@
1
+ All view related aspects of filterrific are handled via the view API:
2
+
3
+ <%= filterrific_form_for @filterrific do |f| %>
4
+
5
+ <%= f.select :author, # look at formtastic for inspiration. It auto generates a bunch of stuff %>
6
+ Options for select:
7
+ * multiple => false
8
+ * allow_blank => true or "-- Any --"
9
+ * value_method => :id
10
+ * label_method => %w[name title label]
11
+ * collection =>
12
+
13
+ <%= f.boolean :published %>
14
+ Renders a checkbox
15
+
16
+ <%= f.search %>
17
+ Renders a text box for searching
18
+ Options:
19
+ * JS: event: keydown, delay, min_chars
20
+ * wildcards here or in model?
21
+
22
+ <%= f.date or f.datetime %>
23
+ Renders a text box for date selection. Integrate with jQuery UI calendar select
24
+ * date is date only
25
+ * datetime also adds time
26
+
27
+ <%= f.radio %>
28
+ Renders a radio button to select one of many options
29
+ Options
30
+ * label_methods => see select
31
+ * value_method => :id
32
+ * collection =>
33
+
34
+ <%= f.sort %>
35
+ Renders a select for choosing sort options
36
+ Options
37
+ * collection =>
38
+
39
+ <% end %>
40
+
41
+
42
+ <%= filterrific_info(options) %>
43
+
44
+ Options:
45
+ * :print_all_available_filters
46
+ * :print_current_filter_params
47
+
48
+
49
+ Also have to add JS to observe the form. Use generator for that?
data/doc/documentation.md CHANGED
@@ -14,8 +14,10 @@
14
14
 
15
15
  * Scenarios
16
16
  * Saved search (persist FilterrificParamSet in DB)
17
- * Will paginate integration
17
+ * Will paginate/kaminari integration
18
18
  * Session persistence
19
+ * Interface for JS client side app to get collections of data via JSON REST
20
+ * integrate with PG fulltext search (pg_search)
19
21
  * Defining Scopes
20
22
  * Belongs_to
21
23
  * Has_many
@@ -27,6 +29,6 @@
27
29
 
28
30
  ## Requirements
29
31
 
30
- * Rails3
32
+ * Rails3 (or 3.1?)
31
33
 
32
34
 
data/doc/ideas.txt CHANGED
@@ -1,3 +1,43 @@
1
+ 20110418: Look at this when building API: https://github.com/ryan-allen/lispy
2
+
3
+
4
+ 20110414 Related project:
5
+
6
+ * https://github.com/plataformatec/has_scope
7
+
8
+ 20110221 Promotion:
9
+
10
+ * railscasts (even ask for review/feedback)
11
+ * railsinside
12
+ * ruby toolbox
13
+
14
+
15
+ 20110205: figuring out best prefix for namespacing filterrific
16
+
17
+ balance between shortness and expression:
18
+
19
+ f
20
+ fc
21
+ ft
22
+ frc
23
+ ftf
24
+ fifc
25
+ flfc
26
+ frfc
27
+ ftfc
28
+ ftrfc
29
+ fltrfc
30
+ filtrfc
31
+ filterrific
32
+
33
+ 20110112: see if I can learn something here:
34
+ http://www.idolhands.com/ruby-on-rails/guides-tips-and-tutorials/add-filters-to-views-using-named-scopes-in-rails
35
+
36
+
37
+ 20101227: get inspiration from this project: https://github.com/neerajdotname/admin_data
38
+ look especially at their query builder in the heroku demo project (add conditions like Finder search)
39
+
40
+
1
41
  Glean specific code from these projects:
2
42
 
3
43
  20100224 CVIMS
@@ -6,28 +46,6 @@ Glean specific code from these projects:
6
46
  201001 Quentin-rails-backend
7
47
  stratadocs (list starts with self as AR proxy)
8
48
 
9
- MODEL
10
- =====
11
-
12
- filterrific do
13
-
14
- associations do
15
- person # creates a scope for filterrific
16
- state, :default => [1,2,3,4], :type => :select, :multiple => 4
17
- end
18
-
19
- scopes do
20
- active_only, :default => true # refers to already defined scope
21
- search, :as => "q" (optional param name override)
22
- sorted_by, :default => "created_at_desc"
23
- end
24
-
25
- persist_in_session false # overrides global filterrific settings
26
- end
27
-
28
- scope :search, ...
29
-
30
- Only filters defined in filterrific will be exposed via URL. Other scopes will not be accessible
31
49
 
32
50
  view
33
51
  (skip these for now?)
@@ -36,30 +54,8 @@ integrate with formtastic?
36
54
 
37
55
 
38
56
 
39
- CONTROLLER
40
- ==========
41
-
42
- def index
43
- @filter_options = init_filterrific(Publication, :publications_list)
44
- @publications = Publication.filterrific(@filter_options).paginate....
45
- @publications = current_user.publications.filterrific(@filter_options).paginate....
46
- ...
47
- end
48
-
49
- def init_filterrific(klass, scope = nil)
50
- scope_name ||= klass.to_s.underscore # use resource class name as default scope name
51
- filter_options_hash = params[:filter_options]
52
- filter_options_hash ||= session[scope_name] if filterrific.persist_in_session
53
- @filter_options = Filterrific.new(Publication, params[:filter_options])
54
- session[scope_name] = @filter_options.to_hash if filterrific.persist_in_session # (store sanitized options). Maybe just define marshal dump? skip to_hash
55
- end
56
57
 
57
58
 
58
- Filterrific CONFIG
59
- ==================
60
-
61
- Settings:
62
- * persist_in_session
63
59
 
64
60
 
65
61
 
data/doc/todo.md CHANGED
@@ -1,8 +1,21 @@
1
+ To make it work on Rails 3.1:
2
+
3
+ * follow kaminari gem for recipe
4
+ * implement new model api
5
+ * leave view and controller as is
6
+ * bump version to 1.0.0 (not backwards compatible)
7
+
8
+ Later:
9
+ * build filterrific-recipes on github wiki
10
+ * update readme
11
+ * update view and controller apis
12
+
13
+
1
14
  # Implementation Plan for Filterrific
2
15
 
3
16
  ## Version 0.1.0: Rebuild current Rails2 functionality
4
17
 
5
18
  ## Add DSL and scope generator
6
-
19
+ ## Write specs (http://avdi.org/devblog/2011/04/07/rspec-is-for-the-literate/ also read comments about be_...!)
7
20
  ## Add Filterrific form builder
8
21
 
data/filterrific.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{filterrific}
8
- s.version = "0.2.0"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jo Hund"]
12
- s.date = %q{2010-08-25}
12
+ s.date = %q{2011-11-09}
13
13
  s.description = %q{
14
14
  The Rails User Interface solution for filtering your ActiveRecord lists:
15
15
 
@@ -23,47 +23,44 @@ Gem::Specification.new do |s|
23
23
  s.email = %q{jhund@clearcove.ca}
24
24
  s.extra_rdoc_files = [
25
25
  "LICENSE",
26
- "README.rdoc"
26
+ "README.rdoc"
27
27
  ]
28
28
  s.files = [
29
- ".gitignore",
30
- "CHANGELOG.md",
31
- "LICENSE",
32
- "README.rdoc",
33
- "Rakefile",
34
- "VERSION",
35
- "doc/Overview diagram.graffle/QuickLook/Preview.pdf",
36
- "doc/Overview diagram.graffle/QuickLook/Thumbnail.tiff",
37
- "doc/Overview diagram.graffle/data.plist",
38
- "doc/Overview diagram.graffle/image1.tiff",
39
- "doc/documentation.md",
40
- "doc/ideas.txt",
41
- "doc/todo.md",
42
- "doc/workflow.md",
43
- "filterrific.gemspec",
44
- "lib/filterrific.rb",
45
- "lib/filterrific/model_mixin.rb",
46
- "lib/filterrific/param_set.rb",
47
- "lib/filterrific/railtie.rb",
48
- "lib/filterrific/view_helpers.rb",
49
- "test/helper.rb",
50
- "test/test_filterrific.rb"
29
+ "CHANGELOG.md",
30
+ "LICENSE",
31
+ "README.rdoc",
32
+ "Rakefile",
33
+ "VERSION",
34
+ "doc/Overview diagram.graffle/QuickLook/Preview.pdf",
35
+ "doc/Overview diagram.graffle/QuickLook/Thumbnail.tiff",
36
+ "doc/Overview diagram.graffle/data.plist",
37
+ "doc/Overview diagram.graffle/image1.tiff",
38
+ "doc/development_notes/api_design.txt",
39
+ "doc/development_notes/controller_api.txt",
40
+ "doc/development_notes/model_api.rb",
41
+ "doc/development_notes/view_api.txt",
42
+ "doc/documentation.md",
43
+ "doc/ideas.txt",
44
+ "doc/todo.md",
45
+ "doc/workflow.md",
46
+ "filterrific.gemspec",
47
+ "lib/filterrific.rb",
48
+ "lib/filterrific/model_mixin.rb",
49
+ "lib/filterrific/param_set.rb",
50
+ "lib/filterrific/railtie.rb",
51
+ "lib/filterrific/view_helpers.rb",
52
+ "test/helper.rb",
53
+ "test/test_filterrific.rb"
51
54
  ]
52
55
  s.homepage = %q{http://github.com/jhund/filterrific}
53
- s.rdoc_options = ["--charset=UTF-8"]
54
56
  s.require_paths = ["lib"]
55
- s.rubygems_version = %q{1.3.6}
57
+ s.rubygems_version = %q{1.5.2}
56
58
  s.summary = %q{The Rails User Interface solution for filtering your ActiveRecord lists.}
57
- s.test_files = [
58
- "test/helper.rb",
59
- "test/test_filterrific.rb"
60
- ]
61
59
 
62
60
  if s.respond_to? :specification_version then
63
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
64
61
  s.specification_version = 3
65
62
 
66
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
67
64
  s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
68
65
  s.add_development_dependency(%q<yard>, [">= 0"])
69
66
  else
@@ -8,12 +8,17 @@ module Filterrific::ModelMixin
8
8
 
9
9
  # Adds filterrific behavior to class when called like so:
10
10
  #
11
- # filterrific, :defaults => { :sorted_by => "created_at_asc" }
11
+ # filterrific(
12
+ # :defaults => { :sorted_by => "created_at_asc" },
13
+ # :scope_names => [:sorted_by, :search_query, :with_state]
14
+ # )
12
15
  #
13
16
  def filterrific(options = {})
14
17
  # send :include, InstanceMethods
15
18
  cattr_accessor :default_filterrific_params
19
+ cattr_accessor :filterrific_scope_names
16
20
  self.default_filterrific_params = (options[:defaults] || {}).stringify_keys
21
+ self.filterrific_scope_names = (options[:scope_names] || []).stringify_keys
17
22
  end
18
23
 
19
24
  # Returns AR relation based on given filterrific_param_set.
@@ -38,11 +43,6 @@ module Filterrific::ModelMixin
38
43
  ar_proxy
39
44
  end
40
45
 
41
- # Returns Array all filter scope names
42
- def filterrific_scope_names
43
- scopes.map{ |s| s.first.to_s }
44
- end
45
-
46
46
  end
47
47
 
48
48
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filterrific
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 23
5
+ prerelease:
5
6
  segments:
7
+ - 1
6
8
  - 0
7
- - 2
8
9
  - 0
9
- version: 0.2.0
10
+ version: 1.0.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Jo Hund
@@ -14,16 +15,18 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-08-25 00:00:00 -07:00
18
+ date: 2011-11-09 00:00:00 -08:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: thoughtbot-shoulda
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ">="
26
28
  - !ruby/object:Gem::Version
29
+ hash: 3
27
30
  segments:
28
31
  - 0
29
32
  version: "0"
@@ -33,9 +36,11 @@ dependencies:
33
36
  name: yard
34
37
  prerelease: false
35
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
36
40
  requirements:
37
41
  - - ">="
38
42
  - !ruby/object:Gem::Version
43
+ hash: 3
39
44
  segments:
40
45
  - 0
41
46
  version: "0"
@@ -51,7 +56,6 @@ extra_rdoc_files:
51
56
  - LICENSE
52
57
  - README.rdoc
53
58
  files:
54
- - .gitignore
55
59
  - CHANGELOG.md
56
60
  - LICENSE
57
61
  - README.rdoc
@@ -61,6 +65,10 @@ files:
61
65
  - doc/Overview diagram.graffle/QuickLook/Thumbnail.tiff
62
66
  - doc/Overview diagram.graffle/data.plist
63
67
  - doc/Overview diagram.graffle/image1.tiff
68
+ - doc/development_notes/api_design.txt
69
+ - doc/development_notes/controller_api.txt
70
+ - doc/development_notes/model_api.rb
71
+ - doc/development_notes/view_api.txt
64
72
  - doc/documentation.md
65
73
  - doc/ideas.txt
66
74
  - doc/todo.md
@@ -78,31 +86,34 @@ homepage: http://github.com/jhund/filterrific
78
86
  licenses: []
79
87
 
80
88
  post_install_message:
81
- rdoc_options:
82
- - --charset=UTF-8
89
+ rdoc_options: []
90
+
83
91
  require_paths:
84
92
  - lib
85
93
  required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
86
95
  requirements:
87
96
  - - ">="
88
97
  - !ruby/object:Gem::Version
98
+ hash: 3
89
99
  segments:
90
100
  - 0
91
101
  version: "0"
92
102
  required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
93
104
  requirements:
94
105
  - - ">="
95
106
  - !ruby/object:Gem::Version
107
+ hash: 3
96
108
  segments:
97
109
  - 0
98
110
  version: "0"
99
111
  requirements: []
100
112
 
101
113
  rubyforge_project:
102
- rubygems_version: 1.3.6
114
+ rubygems_version: 1.5.2
103
115
  signing_key:
104
116
  specification_version: 3
105
117
  summary: The Rails User Interface solution for filtering your ActiveRecord lists.
106
- test_files:
107
- - test/helper.rb
108
- - test/test_filterrific.rb
118
+ test_files: []
119
+
data/.gitignore DELETED
@@ -1,17 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## PROJECT::GENERAL
5
- *.gem
6
- coverage
7
- rdoc
8
- pkg
9
- tmp
10
- log
11
- .yardoc
12
- measurements
13
-
14
- ## BUNDLER
15
- .bundle
16
- Gemfile.local
17
- Gemfile.lock