presenting 2.0.0 → 2.0.1

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.
Files changed (63) hide show
  1. data/LICENSE +20 -20
  2. data/README +10 -10
  3. data/Rakefile +22 -22
  4. data/app/assets/javascripts/search.js +13 -13
  5. data/app/assets/stylesheets/details-color.css +7 -7
  6. data/app/assets/stylesheets/details.css +10 -10
  7. data/app/assets/stylesheets/form.css +1 -1
  8. data/app/assets/stylesheets/grid-color.css +71 -71
  9. data/app/assets/stylesheets/grid.css +64 -64
  10. data/app/assets/stylesheets/search-color.css +16 -16
  11. data/app/assets/stylesheets/search.css +45 -45
  12. data/app/controllers/presentation/assets_controller.rb +42 -42
  13. data/app/views/presentations/_details.erb +11 -11
  14. data/app/views/presentations/_field_search.erb +14 -14
  15. data/app/views/presentations/_form.erb +20 -20
  16. data/app/views/presentations/_grid.erb +70 -70
  17. data/app/views/presentations/_search.erb +7 -7
  18. data/lib/presentation/base.rb +31 -31
  19. data/lib/presentation/details.rb +21 -21
  20. data/lib/presentation/field_search.rb +67 -67
  21. data/lib/presentation/form.rb +149 -149
  22. data/lib/presentation/grid.rb +162 -160
  23. data/lib/presentation/search.rb +9 -9
  24. data/lib/presenting/attribute.rb +51 -51
  25. data/lib/presenting/configurable.rb +10 -10
  26. data/lib/presenting/defaults.rb +10 -10
  27. data/lib/presenting/field_set.rb +26 -26
  28. data/lib/presenting/form_helpers.rb +51 -51
  29. data/lib/presenting/helpers.rb +114 -114
  30. data/lib/presenting/sanitize.rb +19 -19
  31. data/lib/presenting/search.rb +185 -185
  32. data/lib/presenting/sorting.rb +87 -87
  33. data/test/attribute_test.rb +61 -61
  34. data/test/configurable_test.rb +20 -20
  35. data/test/details_test.rb +68 -68
  36. data/test/field_search_test.rb +102 -102
  37. data/test/field_set_test.rb +46 -46
  38. data/test/grid_test.rb +246 -239
  39. data/test/helpers_test.rb +72 -72
  40. data/test/presenting_test.rb +15 -15
  41. data/test/r3/Gemfile +7 -7
  42. data/test/r3/Gemfile.lock +86 -82
  43. data/test/r3/config/application.rb +12 -12
  44. data/test/r3/config/boot.rb +6 -6
  45. data/test/r3/config/database.yml +22 -22
  46. data/test/r3/config/environment.rb +5 -5
  47. data/test/r3/config/environments/test.rb +35 -35
  48. data/test/r3/db/test.sqlite3 +0 -0
  49. data/test/r3/log/test.log +336 -0
  50. data/test/r3/public/javascripts/presenting/grid.js +0 -0
  51. data/test/r3/public/javascripts/presenting/search.js +13 -13
  52. data/test/r3/public/stylesheets/presenting/details-color.css +7 -7
  53. data/test/r3/public/stylesheets/presenting/details.css +10 -10
  54. data/test/r3/public/stylesheets/presenting/form.css +1 -1
  55. data/test/r3/public/stylesheets/presenting/grid-color.css +71 -71
  56. data/test/r3/public/stylesheets/presenting/grid.css +64 -64
  57. data/test/r3/public/stylesheets/presenting/search-color.css +16 -16
  58. data/test/r3/public/stylesheets/presenting/search.css +45 -45
  59. data/test/sanitize_test.rb +15 -15
  60. data/test/search_conditions_test.rb +137 -137
  61. data/test/search_test.rb +30 -30
  62. data/test/sorting_test.rb +63 -63
  63. metadata +74 -74
@@ -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
@@ -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
@@ -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
+