presenting 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+