presenting 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -20
- data/README +10 -10
- data/Rakefile +22 -22
- data/app/assets/javascripts/search.js +13 -13
- data/app/assets/stylesheets/details-color.css +7 -7
- data/app/assets/stylesheets/details.css +10 -10
- data/app/assets/stylesheets/form.css +1 -1
- data/app/assets/stylesheets/grid-color.css +71 -71
- data/app/assets/stylesheets/grid.css +64 -64
- data/app/assets/stylesheets/search-color.css +16 -16
- data/app/assets/stylesheets/search.css +45 -45
- data/app/controllers/presentation/assets_controller.rb +42 -42
- data/app/views/presentations/_details.erb +11 -11
- data/app/views/presentations/_field_search.erb +14 -14
- data/app/views/presentations/_form.erb +20 -20
- data/app/views/presentations/_grid.erb +70 -70
- data/app/views/presentations/_search.erb +7 -7
- data/lib/presentation/base.rb +31 -31
- data/lib/presentation/details.rb +21 -21
- data/lib/presentation/field_search.rb +67 -67
- data/lib/presentation/form.rb +149 -149
- data/lib/presentation/grid.rb +162 -160
- data/lib/presentation/search.rb +9 -9
- data/lib/presenting/attribute.rb +51 -51
- data/lib/presenting/configurable.rb +10 -10
- data/lib/presenting/defaults.rb +10 -10
- data/lib/presenting/field_set.rb +26 -26
- data/lib/presenting/form_helpers.rb +51 -51
- data/lib/presenting/helpers.rb +114 -114
- data/lib/presenting/sanitize.rb +19 -19
- data/lib/presenting/search.rb +185 -185
- data/lib/presenting/sorting.rb +87 -87
- data/test/attribute_test.rb +61 -61
- data/test/configurable_test.rb +20 -20
- data/test/details_test.rb +68 -68
- data/test/field_search_test.rb +102 -102
- data/test/field_set_test.rb +46 -46
- data/test/grid_test.rb +246 -239
- data/test/helpers_test.rb +72 -72
- data/test/presenting_test.rb +15 -15
- data/test/r3/Gemfile +7 -7
- data/test/r3/Gemfile.lock +86 -82
- data/test/r3/config/application.rb +12 -12
- data/test/r3/config/boot.rb +6 -6
- data/test/r3/config/database.yml +22 -22
- data/test/r3/config/environment.rb +5 -5
- data/test/r3/config/environments/test.rb +35 -35
- data/test/r3/db/test.sqlite3 +0 -0
- data/test/r3/log/test.log +336 -0
- data/test/r3/public/javascripts/presenting/grid.js +0 -0
- data/test/r3/public/javascripts/presenting/search.js +13 -13
- data/test/r3/public/stylesheets/presenting/details-color.css +7 -7
- data/test/r3/public/stylesheets/presenting/details.css +10 -10
- data/test/r3/public/stylesheets/presenting/form.css +1 -1
- data/test/r3/public/stylesheets/presenting/grid-color.css +71 -71
- data/test/r3/public/stylesheets/presenting/grid.css +64 -64
- data/test/r3/public/stylesheets/presenting/search-color.css +16 -16
- data/test/r3/public/stylesheets/presenting/search.css +45 -45
- data/test/sanitize_test.rb +15 -15
- data/test/search_conditions_test.rb +137 -137
- data/test/search_test.rb +30 -30
- data/test/sorting_test.rb +63 -63
- metadata +74 -74
data/lib/presenting/search.rb
CHANGED
@@ -1,185 +1,185 @@
|
|
1
|
-
module Presenting
|
2
|
-
class Search
|
3
|
-
include Presenting::Configurable
|
4
|
-
|
5
|
-
# I want to support three configuration formats:
|
6
|
-
#
|
7
|
-
# Search.new(:fields => [:first_name, :last_name, :email])
|
8
|
-
#
|
9
|
-
# Search.new(:fields => {
|
10
|
-
# 'first_name' => :equals,
|
11
|
-
# 'last_name' => :begins_with,
|
12
|
-
# 'email' => :not_null
|
13
|
-
# })
|
14
|
-
#
|
15
|
-
# Search.new(:fields => {
|
16
|
-
# 'fname' => {:sql => 'first_name', :pattern => :equals},
|
17
|
-
# 'lname' => {:sql => 'last_name', :pattern => :begins_with},
|
18
|
-
# 'email' => {:sql => 'email', :pattern => :not_null}
|
19
|
-
# })
|
20
|
-
def fields=(obj)
|
21
|
-
case obj
|
22
|
-
when Array
|
23
|
-
obj.each do |name| fields << name end
|
24
|
-
|
25
|
-
when Hash
|
26
|
-
obj.each do |k, v|
|
27
|
-
fields << {k => v}
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def fields
|
33
|
-
@fields ||= FieldSet.new
|
34
|
-
end
|
35
|
-
|
36
|
-
def to_sql(params, type = :simple)
|
37
|
-
send("to_#{type}_sql", params) unless params.blank?
|
38
|
-
end
|
39
|
-
|
40
|
-
protected
|
41
|
-
|
42
|
-
# handles a simple search where a given term is matched against a number of fields, and can match any of them.
|
43
|
-
# this is usually presented to the user as a single "smart" search box.
|
44
|
-
def to_simple_sql(term)
|
45
|
-
sql = fields.map(&:fragment).join(' OR ')
|
46
|
-
binds = fields.collect{|f| f.bind(term)}.compact
|
47
|
-
[sql, binds].flatten.compact
|
48
|
-
end
|
49
|
-
|
50
|
-
# handles a search setup where a user may enter a search value for any field, and anything entered must match.
|
51
|
-
# this is usually presented to the user as a set of labeled search boxes.
|
52
|
-
#
|
53
|
-
# example field terms:
|
54
|
-
# field_terms = {
|
55
|
-
# 'first_name' => {:value => 'Bob'}
|
56
|
-
# 'last_name' => {:value => 'Smith'}
|
57
|
-
# }
|
58
|
-
#
|
59
|
-
def to_field_sql(field_terms)
|
60
|
-
searched_fields = fields.select{|f| field_terms[f.name] and not field_terms[f.name][:value].blank?}
|
61
|
-
unless searched_fields.empty?
|
62
|
-
sql = searched_fields.map(&:fragment).join(' AND ')
|
63
|
-
binds = searched_fields.collect{|f| f.bind(field_terms[f.name][:value])}
|
64
|
-
|
65
|
-
[sql, binds].flatten.compact
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
class FieldSet < Array
|
70
|
-
def <<(val)
|
71
|
-
if val.is_a? Hash
|
72
|
-
k, v = *val.to_a.first
|
73
|
-
opts = v.is_a?(Hash) ? v : {:pattern => v}
|
74
|
-
opts[:name] = k
|
75
|
-
else
|
76
|
-
opts = {:name => val}
|
77
|
-
end
|
78
|
-
super Field.new(opts)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# TODO: a field may require extra joins when it is searched on
|
83
|
-
# TODO: support more than just mysql (need access to a Connection for quoting and attribute conditions)
|
84
|
-
class Field
|
85
|
-
include Presenting::Configurable
|
86
|
-
|
87
|
-
# required (this is what appears in the parameter hash)
|
88
|
-
attr_reader :name
|
89
|
-
def name=(val)
|
90
|
-
@name = val.to_s
|
91
|
-
end
|
92
|
-
|
93
|
-
# sql field (default == name)
|
94
|
-
def sql
|
95
|
-
@sql ||= name
|
96
|
-
end
|
97
|
-
attr_writer :sql
|
98
|
-
|
99
|
-
# a shortcut for common operator/bind_pattern combos
|
100
|
-
def pattern=(val)
|
101
|
-
case val
|
102
|
-
when :equals
|
103
|
-
self.operator = '= ?'
|
104
|
-
self.bind_pattern = '?'
|
105
|
-
when :begins_with
|
106
|
-
self.operator = 'LIKE ?'
|
107
|
-
self.bind_pattern = '?%'
|
108
|
-
when :ends_with
|
109
|
-
self.operator = 'LIKE ?'
|
110
|
-
self.bind_pattern = '%?'
|
111
|
-
when :contains
|
112
|
-
self.operator = 'LIKE ?'
|
113
|
-
self.bind_pattern = '%?%'
|
114
|
-
when :null
|
115
|
-
self.operator = 'IS NULL'
|
116
|
-
when :not_null
|
117
|
-
self.operator = 'IS NOT NULL'
|
118
|
-
when :true
|
119
|
-
self.operator = '= ?'
|
120
|
-
self.bind_pattern = true
|
121
|
-
when :false
|
122
|
-
self.operator = '= ?'
|
123
|
-
self.bind_pattern = false
|
124
|
-
when :less_than
|
125
|
-
self.operator = '< ?'
|
126
|
-
when :less_than_or_equal_to, :not_greater_than
|
127
|
-
self.operator = '<= ?'
|
128
|
-
when :greater_than
|
129
|
-
self.operator = '> ?'
|
130
|
-
when :greater_than_or_equal_to, :not_less_than
|
131
|
-
self.operator = '>= ?'
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# the format for comparison with :sql, with an optional bind for search terms
|
136
|
-
# '= ?', 'LIKE ?', 'IN (?)', etc.
|
137
|
-
def operator
|
138
|
-
@operator ||= '= ?'
|
139
|
-
end
|
140
|
-
attr_writer :operator
|
141
|
-
|
142
|
-
# formats the term BEFORE binding into the sql
|
143
|
-
# e.g. '?', '?%', etc.
|
144
|
-
def bind_pattern
|
145
|
-
@bind_pattern ||= '?'
|
146
|
-
end
|
147
|
-
attr_writer :bind_pattern
|
148
|
-
|
149
|
-
# composes the sql fragment
|
150
|
-
def fragment
|
151
|
-
"#{sql} #{operator}"
|
152
|
-
end
|
153
|
-
|
154
|
-
# prepares the bindable term
|
155
|
-
def bind(term)
|
156
|
-
return nil unless operator.include?('?')
|
157
|
-
return bind_pattern unless bind_pattern.is_a? String
|
158
|
-
bind_pattern == '?' ? typecast(term) : bind_pattern.sub('?', typecast(term).to_s)
|
159
|
-
end
|
160
|
-
|
161
|
-
# you can set a data type for the field, which will be used to convert
|
162
|
-
# parameter values. currently this is mostly useful for :time searches.
|
163
|
-
attr_accessor :type
|
164
|
-
|
165
|
-
protected
|
166
|
-
|
167
|
-
def typecast(val)
|
168
|
-
case type
|
169
|
-
when :date
|
170
|
-
val.is_a?(String) ?
|
171
|
-
(Time.zone ? Time.zone.parse(val) : Time.parse(val)).to_date :
|
172
|
-
val
|
173
|
-
|
174
|
-
when :time, :datetime
|
175
|
-
val.is_a?(String) ?
|
176
|
-
(Time.zone ? Time.zone.parse(val) : Time.parse(val)) :
|
177
|
-
val
|
178
|
-
|
179
|
-
else
|
180
|
-
val.to_s.strip
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
1
|
+
module Presenting
|
2
|
+
class Search
|
3
|
+
include Presenting::Configurable
|
4
|
+
|
5
|
+
# I want to support three configuration formats:
|
6
|
+
#
|
7
|
+
# Search.new(:fields => [:first_name, :last_name, :email])
|
8
|
+
#
|
9
|
+
# Search.new(:fields => {
|
10
|
+
# 'first_name' => :equals,
|
11
|
+
# 'last_name' => :begins_with,
|
12
|
+
# 'email' => :not_null
|
13
|
+
# })
|
14
|
+
#
|
15
|
+
# Search.new(:fields => {
|
16
|
+
# 'fname' => {:sql => 'first_name', :pattern => :equals},
|
17
|
+
# 'lname' => {:sql => 'last_name', :pattern => :begins_with},
|
18
|
+
# 'email' => {:sql => 'email', :pattern => :not_null}
|
19
|
+
# })
|
20
|
+
def fields=(obj)
|
21
|
+
case obj
|
22
|
+
when Array
|
23
|
+
obj.each do |name| fields << name end
|
24
|
+
|
25
|
+
when Hash
|
26
|
+
obj.each do |k, v|
|
27
|
+
fields << {k => v}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def fields
|
33
|
+
@fields ||= FieldSet.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_sql(params, type = :simple)
|
37
|
+
send("to_#{type}_sql", params) unless params.blank?
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# handles a simple search where a given term is matched against a number of fields, and can match any of them.
|
43
|
+
# this is usually presented to the user as a single "smart" search box.
|
44
|
+
def to_simple_sql(term)
|
45
|
+
sql = fields.map(&:fragment).join(' OR ')
|
46
|
+
binds = fields.collect{|f| f.bind(term)}.compact
|
47
|
+
[sql, binds].flatten.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
# handles a search setup where a user may enter a search value for any field, and anything entered must match.
|
51
|
+
# this is usually presented to the user as a set of labeled search boxes.
|
52
|
+
#
|
53
|
+
# example field terms:
|
54
|
+
# field_terms = {
|
55
|
+
# 'first_name' => {:value => 'Bob'}
|
56
|
+
# 'last_name' => {:value => 'Smith'}
|
57
|
+
# }
|
58
|
+
#
|
59
|
+
def to_field_sql(field_terms)
|
60
|
+
searched_fields = fields.select{|f| field_terms[f.name] and not field_terms[f.name][:value].blank?}
|
61
|
+
unless searched_fields.empty?
|
62
|
+
sql = searched_fields.map(&:fragment).join(' AND ')
|
63
|
+
binds = searched_fields.collect{|f| f.bind(field_terms[f.name][:value])}
|
64
|
+
|
65
|
+
[sql, binds].flatten.compact
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class FieldSet < Array
|
70
|
+
def <<(val)
|
71
|
+
if val.is_a? Hash
|
72
|
+
k, v = *val.to_a.first
|
73
|
+
opts = v.is_a?(Hash) ? v : {:pattern => v}
|
74
|
+
opts[:name] = k
|
75
|
+
else
|
76
|
+
opts = {:name => val}
|
77
|
+
end
|
78
|
+
super Field.new(opts)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# TODO: a field may require extra joins when it is searched on
|
83
|
+
# TODO: support more than just mysql (need access to a Connection for quoting and attribute conditions)
|
84
|
+
class Field
|
85
|
+
include Presenting::Configurable
|
86
|
+
|
87
|
+
# required (this is what appears in the parameter hash)
|
88
|
+
attr_reader :name
|
89
|
+
def name=(val)
|
90
|
+
@name = val.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
# sql field (default == name)
|
94
|
+
def sql
|
95
|
+
@sql ||= name
|
96
|
+
end
|
97
|
+
attr_writer :sql
|
98
|
+
|
99
|
+
# a shortcut for common operator/bind_pattern combos
|
100
|
+
def pattern=(val)
|
101
|
+
case val
|
102
|
+
when :equals
|
103
|
+
self.operator = '= ?'
|
104
|
+
self.bind_pattern = '?'
|
105
|
+
when :begins_with
|
106
|
+
self.operator = 'LIKE ?'
|
107
|
+
self.bind_pattern = '?%'
|
108
|
+
when :ends_with
|
109
|
+
self.operator = 'LIKE ?'
|
110
|
+
self.bind_pattern = '%?'
|
111
|
+
when :contains
|
112
|
+
self.operator = 'LIKE ?'
|
113
|
+
self.bind_pattern = '%?%'
|
114
|
+
when :null
|
115
|
+
self.operator = 'IS NULL'
|
116
|
+
when :not_null
|
117
|
+
self.operator = 'IS NOT NULL'
|
118
|
+
when :true
|
119
|
+
self.operator = '= ?'
|
120
|
+
self.bind_pattern = true
|
121
|
+
when :false
|
122
|
+
self.operator = '= ?'
|
123
|
+
self.bind_pattern = false
|
124
|
+
when :less_than
|
125
|
+
self.operator = '< ?'
|
126
|
+
when :less_than_or_equal_to, :not_greater_than
|
127
|
+
self.operator = '<= ?'
|
128
|
+
when :greater_than
|
129
|
+
self.operator = '> ?'
|
130
|
+
when :greater_than_or_equal_to, :not_less_than
|
131
|
+
self.operator = '>= ?'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# the format for comparison with :sql, with an optional bind for search terms
|
136
|
+
# '= ?', 'LIKE ?', 'IN (?)', etc.
|
137
|
+
def operator
|
138
|
+
@operator ||= '= ?'
|
139
|
+
end
|
140
|
+
attr_writer :operator
|
141
|
+
|
142
|
+
# formats the term BEFORE binding into the sql
|
143
|
+
# e.g. '?', '?%', etc.
|
144
|
+
def bind_pattern
|
145
|
+
@bind_pattern ||= '?'
|
146
|
+
end
|
147
|
+
attr_writer :bind_pattern
|
148
|
+
|
149
|
+
# composes the sql fragment
|
150
|
+
def fragment
|
151
|
+
"#{sql} #{operator}"
|
152
|
+
end
|
153
|
+
|
154
|
+
# prepares the bindable term
|
155
|
+
def bind(term)
|
156
|
+
return nil unless operator.include?('?')
|
157
|
+
return bind_pattern unless bind_pattern.is_a? String
|
158
|
+
bind_pattern == '?' ? typecast(term) : bind_pattern.sub('?', typecast(term).to_s)
|
159
|
+
end
|
160
|
+
|
161
|
+
# you can set a data type for the field, which will be used to convert
|
162
|
+
# parameter values. currently this is mostly useful for :time searches.
|
163
|
+
attr_accessor :type
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
def typecast(val)
|
168
|
+
case type
|
169
|
+
when :date
|
170
|
+
val.is_a?(String) ?
|
171
|
+
(Time.zone ? Time.zone.parse(val) : Time.parse(val)).to_date :
|
172
|
+
val
|
173
|
+
|
174
|
+
when :time, :datetime
|
175
|
+
val.is_a?(String) ?
|
176
|
+
(Time.zone ? Time.zone.parse(val) : Time.parse(val)) :
|
177
|
+
val
|
178
|
+
|
179
|
+
else
|
180
|
+
val.to_s.strip
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
data/lib/presenting/sorting.rb
CHANGED
@@ -1,87 +1,87 @@
|
|
1
|
-
module Presenting
|
2
|
-
class Sorting
|
3
|
-
include Presenting::Configurable
|
4
|
-
|
5
|
-
# I want to support two configuration formats:
|
6
|
-
#
|
7
|
-
# Sorting.new(:fields => [:first_name, :last_name, :email])
|
8
|
-
#
|
9
|
-
# Sorting.new(:fields => {
|
10
|
-
# 'name' => 'CONCAT(first_name, last_name)',
|
11
|
-
# 'email' => 'email_address'
|
12
|
-
# })
|
13
|
-
def fields=(obj)
|
14
|
-
case obj
|
15
|
-
when Array
|
16
|
-
obj.each do |name| fields << name end
|
17
|
-
|
18
|
-
when Hash
|
19
|
-
obj.each do |k, v|
|
20
|
-
fields << {k => v}
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def fields
|
26
|
-
@fields ||= FieldSet.new
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_sql(param)
|
30
|
-
fields.each do |field|
|
31
|
-
# search for and return the first known field
|
32
|
-
return "#{field.sql} #{desc_or_asc param[field.name]}" if param[field.name]
|
33
|
-
end unless param.blank?
|
34
|
-
# no known fields found
|
35
|
-
default
|
36
|
-
end
|
37
|
-
|
38
|
-
# The default sorting, if no known fields are found in the parameters.
|
39
|
-
# Default sorting is specified by name/direction, using an array.
|
40
|
-
#
|
41
|
-
# Example:
|
42
|
-
#
|
43
|
-
# @sorting.default = [:name, 'asc']
|
44
|
-
#
|
45
|
-
def default
|
46
|
-
@default ||= "#{fields.first.sql} ASC"
|
47
|
-
end
|
48
|
-
def default=(val)
|
49
|
-
@default = "#{fields.find{|f| f.name == val.first.to_s}.sql} #{desc_or_asc val.second}"
|
50
|
-
end
|
51
|
-
|
52
|
-
protected
|
53
|
-
|
54
|
-
def desc_or_asc(val)
|
55
|
-
val.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
56
|
-
end
|
57
|
-
|
58
|
-
class FieldSet < Array
|
59
|
-
def <<(val)
|
60
|
-
if val.is_a? Hash
|
61
|
-
k, v = *val.to_a.first
|
62
|
-
opts = v.is_a?(Hash) ? v : {:sql => v}
|
63
|
-
opts[:name] = k
|
64
|
-
else
|
65
|
-
opts = {:name => val}
|
66
|
-
end
|
67
|
-
super Field.new(opts)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
class Field
|
72
|
-
include Presenting::Configurable
|
73
|
-
|
74
|
-
# required (this is what appears in the parameter hash)
|
75
|
-
attr_reader :name
|
76
|
-
def name=(val)
|
77
|
-
@name = val.to_s
|
78
|
-
end
|
79
|
-
|
80
|
-
# sql field (default == name)
|
81
|
-
attr_writer :sql
|
82
|
-
def sql
|
83
|
-
@sql ||= self.name.to_s
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
1
|
+
module Presenting
|
2
|
+
class Sorting
|
3
|
+
include Presenting::Configurable
|
4
|
+
|
5
|
+
# I want to support two configuration formats:
|
6
|
+
#
|
7
|
+
# Sorting.new(:fields => [:first_name, :last_name, :email])
|
8
|
+
#
|
9
|
+
# Sorting.new(:fields => {
|
10
|
+
# 'name' => 'CONCAT(first_name, last_name)',
|
11
|
+
# 'email' => 'email_address'
|
12
|
+
# })
|
13
|
+
def fields=(obj)
|
14
|
+
case obj
|
15
|
+
when Array
|
16
|
+
obj.each do |name| fields << name end
|
17
|
+
|
18
|
+
when Hash
|
19
|
+
obj.each do |k, v|
|
20
|
+
fields << {k => v}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def fields
|
26
|
+
@fields ||= FieldSet.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_sql(param)
|
30
|
+
fields.each do |field|
|
31
|
+
# search for and return the first known field
|
32
|
+
return "#{field.sql} #{desc_or_asc param[field.name]}" if param[field.name]
|
33
|
+
end unless param.blank?
|
34
|
+
# no known fields found
|
35
|
+
default
|
36
|
+
end
|
37
|
+
|
38
|
+
# The default sorting, if no known fields are found in the parameters.
|
39
|
+
# Default sorting is specified by name/direction, using an array.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# @sorting.default = [:name, 'asc']
|
44
|
+
#
|
45
|
+
def default
|
46
|
+
@default ||= "#{fields.first.sql} ASC"
|
47
|
+
end
|
48
|
+
def default=(val)
|
49
|
+
@default = "#{fields.find{|f| f.name == val.first.to_s}.sql} #{desc_or_asc val.second}"
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def desc_or_asc(val)
|
55
|
+
val.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
56
|
+
end
|
57
|
+
|
58
|
+
class FieldSet < Array
|
59
|
+
def <<(val)
|
60
|
+
if val.is_a? Hash
|
61
|
+
k, v = *val.to_a.first
|
62
|
+
opts = v.is_a?(Hash) ? v : {:sql => v}
|
63
|
+
opts[:name] = k
|
64
|
+
else
|
65
|
+
opts = {:name => val}
|
66
|
+
end
|
67
|
+
super Field.new(opts)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Field
|
72
|
+
include Presenting::Configurable
|
73
|
+
|
74
|
+
# required (this is what appears in the parameter hash)
|
75
|
+
attr_reader :name
|
76
|
+
def name=(val)
|
77
|
+
@name = val.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
# sql field (default == name)
|
81
|
+
attr_writer :sql
|
82
|
+
def sql
|
83
|
+
@sql ||= self.name.to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/test/attribute_test.rb
CHANGED
@@ -1,61 +1,61 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
class AttributeTest < Presenting::Test
|
4
|
-
|
5
|
-
def setup
|
6
|
-
@a = Presenting::Attribute.new
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_assigning_a_symbol_name
|
10
|
-
@a.name = :foo
|
11
|
-
assert_equal "Foo", @a.name, "name is typecast to a string and titleized"
|
12
|
-
assert_equal :foo, @a.value, "value is assumed to be a symbol as well"
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_assigning_a_string_name
|
16
|
-
@a.name = "foo"
|
17
|
-
assert_equal "foo", @a.name, "name remains a string"
|
18
|
-
assert_equal "foo", @a.value, "value is assumed to be a string"
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_symbol_values
|
22
|
-
@a.value = :foo
|
23
|
-
assert_equal "bar", @a.value_from(stub('row', :foo => "bar")), "symbols are methods"
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_string_values
|
27
|
-
@a.value = "foo"
|
28
|
-
assert_equal "foo", @a.value_from(stub('row', :foo => "bar")), "strings are constant"
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_proc_values
|
32
|
-
@a.value = proc{|row| "hello"}
|
33
|
-
assert_equal "hello", @a.value_from(stub('row', :foo => "bar")), "procs are custom"
|
34
|
-
end
|
35
|
-
|
36
|
-
def test_that_value_from_does_not_sanitizes_itself
|
37
|
-
@a.value = '<span>hello</span>'
|
38
|
-
@a.sanitize = true
|
39
|
-
assert_equal '<span>hello</span>', @a.value_from(nil)
|
40
|
-
end
|
41
|
-
|
42
|
-
def test_hash_rows_with_symbol_values
|
43
|
-
@a.value = :foo
|
44
|
-
assert_equal 'bar', @a.value_from({:foo => 'bar'}), "symbols are hash keys"
|
45
|
-
end
|
46
|
-
|
47
|
-
def test_sanitize_is_default_true
|
48
|
-
assert @a.sanitize?
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_assigning_a_symbol_id
|
52
|
-
@a.id = :foo
|
53
|
-
assert_equal 'foo', @a.id
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_default_id_from_complex_name
|
57
|
-
@a.name = 'Hello, World!'
|
58
|
-
assert_equal 'hello_world', @a.id
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class AttributeTest < Presenting::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@a = Presenting::Attribute.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_assigning_a_symbol_name
|
10
|
+
@a.name = :foo
|
11
|
+
assert_equal "Foo", @a.name, "name is typecast to a string and titleized"
|
12
|
+
assert_equal :foo, @a.value, "value is assumed to be a symbol as well"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_assigning_a_string_name
|
16
|
+
@a.name = "foo"
|
17
|
+
assert_equal "foo", @a.name, "name remains a string"
|
18
|
+
assert_equal "foo", @a.value, "value is assumed to be a string"
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_symbol_values
|
22
|
+
@a.value = :foo
|
23
|
+
assert_equal "bar", @a.value_from(stub('row', :foo => "bar")), "symbols are methods"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_string_values
|
27
|
+
@a.value = "foo"
|
28
|
+
assert_equal "foo", @a.value_from(stub('row', :foo => "bar")), "strings are constant"
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_proc_values
|
32
|
+
@a.value = proc{|row| "hello"}
|
33
|
+
assert_equal "hello", @a.value_from(stub('row', :foo => "bar")), "procs are custom"
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_that_value_from_does_not_sanitizes_itself
|
37
|
+
@a.value = '<span>hello</span>'
|
38
|
+
@a.sanitize = true
|
39
|
+
assert_equal '<span>hello</span>', @a.value_from(nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_hash_rows_with_symbol_values
|
43
|
+
@a.value = :foo
|
44
|
+
assert_equal 'bar', @a.value_from({:foo => 'bar'}), "symbols are hash keys"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_sanitize_is_default_true
|
48
|
+
assert @a.sanitize?
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_assigning_a_symbol_id
|
52
|
+
@a.id = :foo
|
53
|
+
assert_equal 'foo', @a.id
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_default_id_from_complex_name
|
57
|
+
@a.name = 'Hello, World!'
|
58
|
+
assert_equal 'hello_world', @a.id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|