filterable-models 0.0.2

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/.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
+