facet_for 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,6 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ \#*#
6
+ .\#*#
7
+
data/README.textile ADDED
@@ -0,0 +1,8 @@
1
+ h1. facet_for
2
+
3
+ facet_for is a collection of FormBuilder helpers that speed up the process of
4
+ creating complex search forms with "Ransack":http://github.com/ernie/ransack
5
+
6
+ h2. Installation
7
+
8
+ Simply include 'facet_for' in your Gemfile
@@ -0,0 +1,16 @@
1
+ module FacetHelper
2
+ def render_facets(object, view)
3
+ facet_html = ''
4
+
5
+ view.meta_select.meta_select_facets.each do |facet|
6
+ facet_html << FacetFor.create_facet(:object => object,
7
+ :object_name => 'q',
8
+ :type => facet.predicate.to_sym,
9
+ :column_name => facet.column_name,
10
+ :params => params)
11
+ end
12
+ facet_html.html_safe
13
+ end
14
+ end
15
+
16
+ ActionView::Base.send :include, FacetHelper
@@ -0,0 +1,52 @@
1
+ class ActionView::Helpers::FormBuilder
2
+ include ActionView::Helpers::TagHelper
3
+ include ActionView::Helpers::FormTagHelper
4
+ include ActionView::Helpers::FormOptionsHelper
5
+ include ActionView::Helpers::CaptureHelper
6
+ include ActionView::Helpers::AssetTagHelper
7
+
8
+ def facet_for(column, *args)
9
+
10
+ options = args.extract_options!
11
+
12
+ facet_html = ''
13
+
14
+ # Here, we collect information about the facet. What the user doesn't
15
+ # supply, we'll make our best guess for.
16
+
17
+ facet = { }
18
+ facet[:object] = @object
19
+ facet[:object_name] = @object_name
20
+ # Information about the column. This is used to generate the default
21
+ # label, and to pass the right selector to the Ransack fields.
22
+
23
+ facet[:column_name] = column
24
+ facet[:column_type] = options[:column_type]
25
+
26
+ # This is the type of field we will render. If this isn't provided, we'll
27
+ # determine this based on facet_column_type
28
+
29
+ facet[:type] = options[:type]
30
+
31
+ # Did we pass in the params? Send it down to determine if we have a value.
32
+
33
+ facet[:params] = options[:params]
34
+
35
+ # In the specific case of facet_type == :collection, we'll allow the user
36
+ # to specify their own collection. If it's an association, we'll attempt
37
+ # to provide one.
38
+
39
+ facet[:collection] = options[:collection]
40
+
41
+ # Are we labeling it differently?
42
+ facet[:label] = options[:label]
43
+
44
+ @facet = FacetFor::Facet.new(facet)
45
+
46
+ facet_html << @facet.render_facet
47
+
48
+ facet_html.html_safe
49
+ end
50
+
51
+
52
+ end
@@ -1,3 +1,3 @@
1
1
  module FacetFor
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/facet_for.rb CHANGED
@@ -1,176 +1,273 @@
1
1
  require "facet_for/version"
2
+ require "facet_for/form_builder"
3
+ require "facet_for/facet_helper"
4
+
5
+ module FacetFor
6
+
7
+ PREDICATES = [
8
+ ['Contains', :cont],
9
+ ['Doesn\'t Contain', :not_cont],
10
+ ['Starts With', :start],
11
+ ['Doesn\'t Start With', :not_start],
12
+ ['Ends With', :end],
13
+ ['Doesn\'t End With', :not_end],
14
+ ['Between', :between],
15
+ ['Is Null', :null],
16
+ ['Is Not Null', :not_null],
17
+ ['Collection', :collection]
18
+ ]
19
+
20
+ def self.predicates
21
+ PREDICATES
22
+ end
2
23
 
3
- class ActionView::Helpers::FormBuilder
4
- include ActionView::Helpers::TagHelper
5
- include ActionView::Helpers::FormTagHelper
6
- include ActionView::Helpers::FormOptionsHelper
7
- include ActionView::Helpers::CaptureHelper
8
- include ActionView::Helpers::AssetTagHelper
24
+ # We can use reflections to determine has_many and belongs_to associations
9
25
 
10
- def facet_for(column, *args)
26
+ def self.create_facet(*args)
11
27
  options = args.extract_options!
12
28
 
13
- facet_html = ''
29
+ @facet = Facet.new(options)
30
+ @facet.render_facet.html_safe
31
+ end
32
+
14
33
 
15
- # Here, we collect information about the facet. What the user doesn't
16
- # supply, we'll make our best guess for.
34
+ def self.column_options(q)
35
+ column_options = []
36
+ column_options.push([q.klass.to_s, options_for_model(q.klass)])
17
37
 
18
- facet = { }
19
- facet[:model] = @object.klass
38
+ q.context.searchable_associations.each do |association|
39
+ association_model = association.camelcase.singularize.constantize
40
+ column_options.push([association_model.to_s, options_for_model(association_model, association)])
41
+ end
20
42
 
21
- facet[:column] = facet[:model].content_columns.select { |x| x.name == column.to_s }
43
+ column_options
44
+ end
22
45
 
23
- # Information about the column. This is used to generate the default
24
- # label, and to pass the right selector to the Ransack fields.
46
+ def self.field_options(q)
47
+ field_options = options_for_model(q.klass) | q.context.searchable_associations.map { |a| [a.titleize, a.to_sym] }
25
48
 
26
- facet[:column_name] = options[:column_name]
27
- facet[:column_type] = options[:column_type]
49
+ field_options
50
+ end
28
51
 
29
- # This is the type of field we will render. If this isn't provided, we'll
30
- # determine this based on facet_column_type
52
+ def self.options_for_model(model, association = nil, grouped = false)
53
+ options = []
54
+ preface = ''
31
55
 
32
- facet[:type] = options[:type]
56
+ if association # preface field names for cross model displaying
57
+ preface = "#{association}."
58
+ end
33
59
 
34
- # In the specific case of facet_type == :collection, we'll allow the user
35
- # to specify their own collection. If it's an association, we'll attempt
36
- # to provide one.
60
+ if model.ransackable_attributes
61
+ options = model.ransackable_attributes.map { |a| [a.titleize, "#{preface}#{a}".to_sym] }
62
+ end
37
63
 
38
- facet[:collection] = options[:collection]
64
+ options
65
+ end
39
66
 
40
- if facet[:column].empty? # facet doesn't exist
67
+ class Facet
68
+ include ActionView::Helpers::TagHelper
69
+ include ActionView::Helpers::FormTagHelper
70
+ include ActionView::Helpers::FormOptionsHelper
71
+ include ActionView::Helpers::CaptureHelper
72
+ include ActionView::Helpers::AssetTagHelper
41
73
 
42
- # Is it an association?
43
- #
44
- # We check for :singular_name, :plural_name and :singular_id
74
+ attr_accessor :facet
45
75
 
46
- if association = associations_for(facet[:model]).select { |x|
47
- x.plural_name == column.to_s or
48
- x.plural_name.singularize == column.to_s or
49
- x.primary_key_name == column.to_s }.uniq
76
+ def initialize(facet = {})
77
+ @facet = facet
50
78
 
51
- if association.first.macro == :has_many
79
+ # If they didn't provide a model, try and use the search object
52
80
 
53
- # For a has_many relationship, we want the plural name with _id.
54
- # Ransack will then look at the _id column for the associated model.
55
- # This won't work properly on models with nonstandard id column
56
- # names. That's a problem, but whatevs for the time being.
81
+ if @facet[:object] && @facet[:model].nil?
82
+ @facet[:model] = @facet[:object].klass
83
+ end
57
84
 
58
- facet[:column_name] = "#{association.first.plural_name}_id"
85
+ @facet[:column] = @facet[:model].content_columns.select { |x| x.name == @facet[:column_name].to_s }
59
86
 
60
- elsif association.first.macro == :belongs_to
87
+ if @facet[:column].empty? # facet doesn't exist
61
88
 
62
- # If we're dealing with belongs_to, we can assume we just want
63
- # to look at the foreign key on the current model. Much simpler.
89
+ # Is it an association?
90
+ #
91
+ # We check for :singular_name, :plural_name and :singular_id
64
92
 
65
- facet[:column_name] = association.first.foreign_key
93
+ if association = associations.select { |x|
94
+ x.plural_name == @facet[:column_name].to_s or
95
+ x.plural_name.singularize == @facet[:column_name].to_s or
96
+ x.primary_key_name == @facet[:column_name].to_s }.uniq and association.count > 0
66
97
 
67
- end
98
+ if association.first.macro == :has_many
68
99
 
69
- # By default, we want to use a select box with a collection of
70
- # what this model has_many of. If the user has specified something,
71
- # we'll run with what they specified.
100
+ # For a has_many relationship, we want the plural name with _id.
101
+ # Ransack will then look at the _id column for the associated model.
102
+ # This won't work properly on models with nonstandard id column
103
+ # names. That's a problem, but whatevs for the time being.
72
104
 
73
- facet[:type] = facet[:type] || :collection
105
+ @facet[:column_name] = "#{association.first.plural_name}_id"
74
106
 
75
- # If the user hasn't specified a collection, we'll provide one now
76
- # based on this association
107
+ elsif association.first.macro == :belongs_to
77
108
 
78
- facet[:collection] = facet[:collection] || association.first.klass.all
109
+ # If we're dealing with belongs_to, we can assume we just want
110
+ # to look at the foreign key on the current model. Much simpler.
79
111
 
112
+ @facet[:column_name] = association.first.foreign_key
113
+
114
+ end
115
+
116
+ # By default, we want to use a select box with a collection of
117
+ # what this model has_many of. If the user has specified something,
118
+ # we'll run with what they specified.
119
+
120
+ @facet[:type] = @facet[:type] || :collection
121
+
122
+ # If the user hasn't specified a collection, we'll provide one now
123
+ # based on this association
124
+
125
+ @facet[:collection] = @facet[:collection] || association.first.klass.all
126
+ end
80
127
  else
81
- return # nothing found
128
+
129
+ # We found a column. Let's yank some useful information out of it
130
+ @facet[:column] = @facet[:column].first
131
+ @facet[:column_name] = @facet[:column].name
132
+ @facet[:column_type] = @facet[:column_type] || @facet[:column].type
82
133
  end
83
- else
84
134
 
85
- # We found a column. Let's yank some useful information out of it
135
+ @facet[:type] = @facet[:type] || default_facet_type
86
136
 
87
- facet[:column] = facet[:column].first
88
- facet[:column_name] = facet[:column].name
89
- facet[:column_type] = facet[:column_type] || facet[:column].type
90
137
  end
91
138
 
92
- facet[:type] = facet[:type] || default_facet_type(facet)
139
+ def associations
140
+ @facet[:model].reflections.values
141
+ end
93
142
 
143
+ # If the user doesn't pass options[:label], we'll build a default label
144
+ # based on the type of facet we're rendering.
145
+
146
+ def default_label_for_facet
147
+ case @facet[:type]
148
+ when :true, :false
149
+ return ''
150
+ when :null
151
+ return label("#{@facet[:column_name]}_null",
152
+ "#{@facet[:column_name].to_s.humanize} Is Null?")
153
+ when :not_null
154
+ return label("#{@facet[:column_name]}_not_null",
155
+ "#{@facet[:column_name].to_s.humanize} Is Not Null?")
156
+ when :cont
157
+ return label("#{@facet[:column_name]}_cont",
158
+ "#{@facet[:column_name].to_s.humanize} Contains")
159
+ when :not_cont
160
+ return label("#{@facet[:column_name]}_not_cont",
161
+ "#{@facet[:column_name].to_s.humanize} Doesn't Contain")
162
+ when :start
163
+ return label("#{@facet[:column_name]}_start",
164
+ "#{@facet[:column_name].to_s.humanize} Starts With")
165
+ when :not_start
166
+ return label("#{@facet[:column_name]}_not_start",
167
+ "#{@facet[:column_name].to_s.humanize} Doesn't Start With")
168
+ when :end
169
+ return label("#{@facet[:column_name]}_end",
170
+ "#{@facet[:column_name].to_s.humanize} Ends With")
171
+ when :not_end
172
+ return label("#{@facet[:column_name]}_not_end",
173
+ "#{@facet[:column_name].to_s.humanize} Doesn't End With")
174
+ when :between
175
+ return label("#{@facet[:column_name]}",
176
+ "#{@facet[:column_name].to_s.humanize} Between")
177
+ when :gte
178
+ return label("#{@facet[:column_name]}_gte",
179
+ "#{@facet[:column_name].to_s.humanize} Greater Than")
180
+ when :lte
181
+ return label("#{@facet[:column_name]}_lte",
182
+ "#{@facet[:column_name].to_s.humanize} Less Than")
183
+ else
184
+ return label("#{@facet[:column_name]}")
185
+ end
186
+ end
94
187
 
95
- # Insert our label first
188
+ # Now that we have our type, we can render the actual form field for
189
+ # Ransack
96
190
 
97
- facet_html = "<div class=\"label #{additional_classes(facet)}\">"
191
+ def render_facet
98
192
 
99
- if options[:label]
100
- facet_html << self.label(column, options[:label])
101
- else
102
- facet_html << default_label_for_facet(facet)
103
- end
193
+ # Insert our label first
194
+
195
+ facet_html = "<div class=\"facet_label #{additional_classes}\">"
104
196
 
105
- facet_html << "</div>"
197
+ if @facet[:label]
198
+ facet_html << label("#{@facet[:column_name]}", @facet[:label])
199
+ else
200
+ facet_html << default_label_for_facet
201
+ end
106
202
 
107
- # And now the fields
203
+ facet_html << "</div>"
108
204
 
109
- facet_html << render_facet_for(facet)
205
+ # And now the fields
110
206
 
111
- facet_html.html_safe
112
- end
207
+ facet_html << "<div class=\"facet_input #{additional_classes}\">"
113
208
 
114
- # If the user doesn't pass options[:label], we'll build a default label
115
- # based on the type of facet we're rendering.
209
+ case @facet[:type]
210
+ when :cont, :not_cont, :start, :not_start, :end, :not_end, :gteq, :lteq
211
+ facet_html << text_field
212
+ when :collection
213
+ facet_html << facet_collection
214
+ when :null, :not_null, :true, :false
215
+ facet_html << check_box
216
+ when :between
217
+ facet_html << text_field(:predicate => :gteq)
218
+ facet_html << "<span class=\"facet_divider\">&mdash;</span>"
219
+ facet_html << text_field(:predicate => :lteq)
220
+ end
116
221
 
117
- def default_label_for_facet(facet)
118
- case facet[:type]
119
- when :cont
120
- return self.label("#{facet[:column_name]}_cont".to_sym)
121
- else
122
- return self.label("#{facet[:column_name]}")
222
+ facet_html << "</div>"
223
+ facet_html
123
224
  end
124
- end
125
225
 
126
- # Now that we have our type, we can render the actual form field for
127
- # Ransack
128
-
129
- def render_facet_for(facet)
130
-
131
- facet_html = "<div class=\"input #{additional_classes(facet)}\">"
132
-
133
- case facet[:type]
134
- when :cont
135
- facet_html << self.text_field("#{facet[:column_name]}_cont".to_sym)
136
- when :collection
137
- facet_html << self.collection_select("#{facet[:column_name]}_eq".to_sym, facet[:collection], :id, :to_s, :include_blank => true)
138
- when :between
139
- facet_html << "From "
140
- facet_html << self.text_field("#{facet[:column_name]}_gteq".to_sym, :size => 11)
141
- facet_html << "To "
142
- facet_html << self.text_field("#{facet[:column_name]}_lteq".to_sym, :size => 11)
143
- when :gteq
144
- facet_html << self.text_field("#{facet[:column_name]}_gteq".to_sym, :size => 5)
145
- when :lteq
146
- facet_html << self.text_field("#{facet[:column_name]}_gteq".to_sym, :size => 5)
226
+ # If no options[:type] is specified, we'll look at the column type for
227
+ # the column in the model and make an educated guess.
228
+
229
+ def default_facet_type
230
+
231
+ case @facet[:column_type]
232
+ when :string, :text
233
+ return :cont
234
+ when :datetime, :date, :float, :integer, :double
235
+ return :between
236
+ end
147
237
  end
148
238
 
149
- facet_html << "</div>"
150
- facet_html
151
- end
239
+ def additional_classes
240
+ additional_class = "#{@facet[:type]} #{@facet[:column_type]}"
241
+ end
152
242
 
153
- # If no options[:type] is specified, we'll look at the column type for
154
- # the column in the model and make an educated guess.
243
+ def text_field(options = {})
244
+ predicate = options[:predicate] || @facet[:type]
245
+ name = "#{@facet[:column_name]}_#{predicate.to_s}"
155
246
 
156
- def default_facet_type(facet)
247
+ if @facet[:params] and @facet[:params][:q]
248
+ value = @facet[:params][:q][name.to_sym]
249
+ end
157
250
 
158
- case facet[:column_type]
159
- when :string, :text
160
- return :cont
161
- when :datetime, :date, :float, :integer, :double
162
- return :between
251
+ text_field_tag self.name_for(name), value
163
252
  end
164
- end
165
253
 
166
- def additional_classes(facet)
167
- additional_class = "#{facet[:type]} #{facet[:column_type]}"
168
- end
254
+ def check_box
255
+ check_box_tag self.name_for("#{@facet[:column_name]}_#{@facet[:type].to_s}")
256
+ end
169
257
 
170
- # We can use reflections to determine has_many and belongs_to associations
258
+ def facet_collection
259
+ collection_select @facet[:object_name].to_sym,
260
+ "#{@facet[:column_name]}_eq".to_sym, @facet[:collection],
261
+ :id, :to_s, :include_blank => true
262
+ end
171
263
 
172
- def associations_for(model)
173
- model.reflections.values
174
- end
264
+ def label(string_name, string_label = nil)
265
+ display_label = string_label || string_name.humanize
266
+ label_tag self.name_for(string_name), display_label
267
+ end
175
268
 
269
+ def name_for(string_name)
270
+ "#{@facet[:object_name]}[#{string_name}]".to_sym
271
+ end
272
+ end
176
273
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: facet_for
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-19 00:00:00.000000000Z
12
+ date: 2011-10-28 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ransack
16
- requirement: &84397210 !ruby/object:Gem::Requirement
16
+ requirement: &79711100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *84397210
24
+ version_requirements: *79711100
25
25
  description: Provides helpers for creating search forms with Ransack
26
26
  email:
27
27
  - jbarket@sleepunit.com
@@ -31,9 +31,12 @@ extra_rdoc_files: []
31
31
  files:
32
32
  - .gitignore
33
33
  - Gemfile
34
+ - README.textile
34
35
  - Rakefile
35
36
  - facet_for.gemspec
36
37
  - lib/facet_for.rb
38
+ - lib/facet_for/facet_helper.rb
39
+ - lib/facet_for/form_builder.rb
37
40
  - lib/facet_for/version.rb
38
41
  homepage: ''
39
42
  licenses: []