filterrific 0.2.0 → 1.0.0

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