filterable-models 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in filterable-models.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "filterable-models/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "filterable-models"
7
+ s.version = Filterable::Models::VERSION
8
+ s.authors = ["Marcelo Ribeiro"]
9
+ s.email = ["ribeirodesenv@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Filter functionality for Rails models}
12
+ s.description = %q{Start filtering your models and adding a filter form to your scaffolds pretty fast}
13
+
14
+ s.rubyforge_project = "filterable-models"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,230 @@
1
+ module Filterable::Models
2
+ module Base
3
+
4
+ def self.included(recipient)
5
+ recipient.extend(ModelClassMethods)
6
+ recipient.class_eval do
7
+ include ModelInstanceMethods
8
+ end
9
+ end
10
+
11
+ module ModelClassMethods
12
+
13
+ def filters(filters)
14
+ @filters = {}
15
+ @filterable = {}
16
+ @sorted_filterable = []
17
+ @filters = filters
18
+
19
+ if filters.is_a?(Array)
20
+ # Ruby 1.8 hash sorting is unpredictable, so we can use an array
21
+ # to take care of the order and make it behave as expected
22
+ filters.each do |hash|
23
+ prepare_filters(hash)
24
+ end
25
+ else
26
+ # Filters is a hash, so lets just move on
27
+ prepare_filters(filters)
28
+ end
29
+ end
30
+
31
+ def prepare_filters(hash)
32
+ hash.map do |key, value|
33
+ if value.is_a?(Hash)
34
+ value.map do |v_key, v_value|
35
+ @filterable["#{key.to_s.pluralize}.#{v_key}".to_sym] = v_value
36
+ @sorted_filterable << {"#{key.to_s.pluralize}.#{v_key}".to_sym => v_value}
37
+ end
38
+ else
39
+ @filterable[key] = value
40
+ @sorted_filterable << {key => value}
41
+ end
42
+ end
43
+ end
44
+
45
+ def filter_options_for(attribute_name, options = [])
46
+ @filter_options ||= {}
47
+ @filter_options[attribute_name] = options
48
+ end
49
+
50
+ def filter_fields_per_column(fields_per_column = 5)
51
+ @fields_per_column = fields_per_column
52
+ end
53
+
54
+ def filter(filters = {})
55
+ filters ||= {}
56
+ filters = filters[self.to_s.underscore] ||= {}
57
+ @filter_conditions = []
58
+ @filter_conditions_values = []
59
+ @current_joins = []
60
+
61
+ keys = []
62
+
63
+ filters.map do |key, value|
64
+
65
+ unless value.blank?
66
+
67
+ if value.is_a?(Hash)
68
+ value.keys.collect{|k| k.to_s}.sort.each do |v_key|
69
+ v_key = v_key.to_sym
70
+ v_value = value[v_key]
71
+
72
+ keys << "#{key.to_s.pluralize}.#{v_key}".to_sym
73
+ end
74
+ @current_joins = build_joins(@current_joins, filters)
75
+ else
76
+ keys << key.to_sym
77
+ end
78
+
79
+ keys.each do |key|
80
+
81
+ if key.to_s =~ /_from/
82
+ key = key.to_s.gsub("_from", "").to_sym
83
+ elsif key.to_s =~ /_to/
84
+ key = key.to_s.gsub("_to", "").to_sym
85
+ end
86
+
87
+ if @filterable.has_key?(key)
88
+ filter_type = @filterable[key]
89
+
90
+ # related object attribute
91
+ if key.to_s =~ /\./
92
+ v_keys = key.to_s.split(".")
93
+ if filter_type =~ /_range/
94
+ value_from = filters[v_keys[0].singularize.to_sym]["#{v_keys[1]}_from".to_sym]
95
+ value_to = filters[v_keys[0].singularize.to_sym]["#{v_keys[1]}_to".to_sym]
96
+ else
97
+ value = filters[v_keys[0].singularize.to_sym][v_keys[1].to_sym]
98
+ end
99
+ else
100
+ if filter_type =~ /_range/
101
+ value_from = filters["#{key}_from".to_sym]
102
+ value_to = filters["#{key}_to".to_sym]
103
+ else
104
+ value = filters[key]
105
+ end
106
+ end
107
+
108
+ unless value.blank?
109
+ if value == 'false'
110
+ value = false
111
+ end
112
+
113
+ if value == 'true'
114
+ value = true
115
+ end
116
+
117
+ compare_key = key.to_s
118
+ unless key.to_s =~ /\./
119
+ compare_key = "#{self.name.to_s.tableize}.#{key.to_s}"
120
+ end
121
+ case filter_type
122
+ when "string"
123
+ @filter_conditions << "#{compare_key} LIKE ?"
124
+ @filter_conditions_values << "%#{value}%"
125
+ when "exact_string"
126
+ @filter_conditions << "#{compare_key} = ?"
127
+ @filter_conditions_values << value
128
+ when "date_range", "float_range"
129
+ if value_from != "" && value_to != ""
130
+ @filter_conditions << "(#{compare_key} >= ? and #{compare_key} <= ?)"
131
+ @filter_conditions_values << value_from
132
+ @filter_conditions_values << value_to
133
+ elsif value_from != ""
134
+ @filter_conditions << "#{compare_key} >= ?"
135
+ @filter_conditions_values << value_from
136
+ elsif value_to != ""
137
+ @filter_conditions << "#{compare_key} <= ?"
138
+ @filter_conditions_values << value_to
139
+ end
140
+ when "drop_down"
141
+ @filter_conditions << "#{compare_key} = ?"
142
+ @filter_conditions_values << value
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ filter_scope = where([@filter_conditions.join(" AND ")] + @filter_conditions_values).group("#{self.name.tableize}.id")
151
+ if @current_joins.any?
152
+ filter_scope = filter_scope.joins(@current_joins)
153
+ end
154
+
155
+ filter_scope
156
+ end
157
+
158
+ def build_joins(current_joins = {}, filters = {})
159
+ @self_object_name = self.name.underscore
160
+ @self_table_name = @self_object_name.pluralize
161
+ @self_filter_joins = []
162
+
163
+ filters.map do |key, value|
164
+
165
+ if value.is_a?(Hash)
166
+ unless filters_are_empty(value)
167
+
168
+ @self_object = self.new
169
+
170
+ if @self_object.attributes.include?("#{key}_id")
171
+ # belogns to
172
+ @self_filter_joins += [key.to_sym]
173
+ else
174
+ @related_object = key.to_s.camelize.constantize.new
175
+
176
+ if @related_object.attributes.include?("#{@self_object_name}_id")
177
+ # has many - need to figure out how to solve it for has one.
178
+ @self_filter_joins += ["#{key.to_s.pluralize}".to_sym]
179
+ elsif @self_object.methods.include?("#{key}_ids")
180
+ # has and_belongs_to_many
181
+ @self_filter_joins += ["#{key.to_s.pluralize}".to_sym]
182
+ end
183
+
184
+ end
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+ if @self_filter_joins.size > 0
191
+ current_joins ||= []
192
+ current_joins += @self_filter_joins
193
+ end
194
+
195
+ current_joins
196
+ end
197
+
198
+ def filters_are_empty(filters)
199
+ filters.map do |key, value|
200
+ return false unless value.blank?
201
+ end
202
+ true
203
+ end
204
+
205
+ def filterable
206
+ @filterable
207
+ end
208
+
209
+ def filter_options
210
+ @filter_options
211
+ end
212
+
213
+ def fields_per_column
214
+ @fields_per_column ||= 5
215
+ end
216
+
217
+ def filters_hash
218
+ @filters
219
+ end
220
+
221
+ def sorted_filterable
222
+ @sorted_filterable
223
+ end
224
+
225
+ end
226
+
227
+ module ModelInstanceMethods
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,145 @@
1
+ module Filterable::Models
2
+ module Helper
3
+ def filter_form_for(object, params = {}, options = {})
4
+ @params = params
5
+ form_html = %{<form action="" method="get">} + "\n"
6
+ elements_html = ""
7
+
8
+ klass = object.to_s.camelize.constantize
9
+
10
+ return if klass.filterable.blank?
11
+
12
+ filter_fields_count = 1
13
+
14
+ klass.sorted_filterable.each do |filterable|
15
+
16
+ filterable.map do |key, value|
17
+ key = key.to_sym
18
+ value = klass.filterable[key]
19
+
20
+ case value
21
+ when 'string'
22
+ elements_html += text_filter_tag(object, key) + "\n"
23
+ when 'exact_string'
24
+ elements_html += text_filter_tag(object, key) + "\n"
25
+ when 'date_range', 'float_range'
26
+ elements_html += text_filter_tag(object, key, "from") + "\n"
27
+ elements_html += text_filter_tag(object, key, "to") + "\n"
28
+ filter_fields_count += 1
29
+ when 'drop_down'
30
+ elements_html += select_filter_tag(object, key) + "\n"
31
+ end
32
+ end
33
+
34
+ if filter_fields_count >= (klass.fields_per_column)
35
+ elements_html += "</ul><ul>" + "\n"
36
+ filter_fields_count = 1
37
+ else
38
+ filter_fields_count += 1
39
+ end
40
+
41
+ end
42
+
43
+ form_html += content_tag(:ul, elements_html.html_safe) + "\n"
44
+ form_html += %{<div style="clear:both;"><!-- --></div>}.html_safe
45
+ form_html += submit_tag(t("Filter").html_safe)
46
+ form_html += "</form>"
47
+
48
+ content_tag(:div, form_html.html_safe, {:class => "filter-form"}.merge(options)).html_safe
49
+ end
50
+
51
+ def text_filter_tag(object, key, extra = nil)
52
+ filter_element_data = get_filter_element_data(object, key, extra)
53
+ filter_field_label_text = filter_element_data[:label_text]
54
+ filter_field_id = filter_element_data[:element_id]
55
+ filter_field_name = filter_element_data[:element_name]
56
+ filter_field_value = filter_element_data[:element_value]
57
+
58
+ content_tag(:li,
59
+ label_tag(filter_field_id, filter_field_label_text) + "<br/>".html_safe +
60
+ text_field_tag(filter_field_name, filter_field_value)).html_safe
61
+ end
62
+
63
+ def select_filter_tag(object, key, extra = nil)
64
+ filter_element_data = get_filter_element_data(object, key, extra)
65
+ filter_field_label_text = filter_element_data[:label_text]
66
+ filter_field_id = filter_element_data[:element_id]
67
+ filter_field_name = filter_element_data[:element_name]
68
+ filter_field_value = filter_element_data[:element_value]
69
+
70
+ content_tag(:li,
71
+ label_tag(filter_field_id, filter_field_label_text) + "<br/>".html_safe +
72
+ select_tag(filter_field_name, select_values_for(object, key, filter_field_value))).html_safe
73
+ end
74
+
75
+ def select_values_for(object, key, value)
76
+ klass = object.to_s.camelize.constantize
77
+ filter_options = klass.filter_options[key]
78
+
79
+ options_html = content_tag(:option, t("Please Select"), :value => "")
80
+
81
+ if filter_options.is_a?(Hash)
82
+ filter_options.keys.collect{|k| k.to_s}.sort.each do |option_key|
83
+ selected = {}
84
+ if value == filter_options[option_key]
85
+ selected = {:selected => "selected"}
86
+ end
87
+ options_html += content_tag(:option, t(option_key.to_s.titleize), {:value => filter_options[option_key]}.merge(selected))
88
+ end
89
+ elsif filter_options.is_a?(Array)
90
+ filter_options.each do |option|
91
+ selected = {}
92
+ if value == option
93
+ selected = {:selected => "selected"}
94
+ end
95
+ options_html += content_tag(:option, t(option.titleize), {:value => option}.merge(selected))
96
+ end
97
+ end
98
+
99
+ options_html.html_safe
100
+ end
101
+
102
+ def get_filter_element_data(object, key, extra)
103
+ params_prefix = :filters
104
+ @params ||= {}
105
+ @params[params_prefix] ||= {}
106
+ @params[params_prefix][object] ||= {}
107
+
108
+ if key.to_s =~ /\./
109
+ key_parts = key.to_s.split(".")
110
+ key_object = key_parts.first.singularize.to_sym
111
+ key_object_att = key_parts.last.to_sym
112
+
113
+ @params[params_prefix][object][key_object] ||= {}
114
+ key = "#{key_object}_#{key_object_att}"
115
+
116
+ if extra.nil?
117
+ element_name = "#{params_prefix}[#{object}][#{key_object}][#{key_object_att}]"
118
+ element_value = @params[params_prefix][object][key_object][key_object_att]
119
+ element_id = "#{params_prefix}_#{object}_#{key}"
120
+ else
121
+ element_name = "#{params_prefix}[#{object}][#{key_object}][#{key_object_att}_#{extra}]"
122
+ element_value = @params[params_prefix][object][key_object]["#{key_object_att}_#{extra}"]
123
+ element_id = "#{params_prefix}_#{object}_#{key}_#{extra}"
124
+ end
125
+ else
126
+ if extra.nil?
127
+ element_name = "#{params_prefix}[#{object}][#{key}]"
128
+ element_value = @params[params_prefix][object][key]
129
+ element_id = "#{params_prefix}_#{object}_#{key}"
130
+ else
131
+ element_name = "#{params_prefix}[#{object}][#{key}_#{extra}]"
132
+ element_value = @params[params_prefix][object]["#{key}_#{extra}".to_sym]
133
+ element_id = "#{params_prefix}_#{object}_#{key}_#{extra}"
134
+ end
135
+ end
136
+
137
+ element_data = {}
138
+ element_data[:label_text] = t("#{key.to_s.humanize}#{extra ? "_#{extra}" : ""}".to_s.titleize) + ": "
139
+ element_data[:element_id] = element_id
140
+ element_data[:element_name] = element_name
141
+ element_data[:element_value] = element_value
142
+ element_data
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,5 @@
1
+ module Filterable
2
+ module Models
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require "filterable-models/version"
2
+ require "filterable-models/base"
3
+ require "filterable-models/helper"
4
+
5
+ ActiveRecord::Base.send :include, Filterable::Models::Base
6
+ ActionView::Helpers::FormTagHelper.send :include, Filterable::Models::Helper
7
+ ActionView::Base.send :include, Filterable::Models::Helper
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filterable-models
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Marcelo Ribeiro
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-09-04 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Start filtering your models and adding a filter form to your scaffolds pretty fast
22
+ email:
23
+ - ribeirodesenv@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - Rakefile
34
+ - filterable-models.gemspec
35
+ - lib/filterable-models.rb
36
+ - lib/filterable-models/base.rb
37
+ - lib/filterable-models/helper.rb
38
+ - lib/filterable-models/version.rb
39
+ homepage: ""
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ hash: 3
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ requirements: []
66
+
67
+ rubyforge_project: filterable-models
68
+ rubygems_version: 1.8.10
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Filter functionality for Rails models
72
+ test_files: []
73
+