17ed30246559af2f1da6e9a4ee9bdb814801e8e0
[feedcatcher.git] / vendor / rails / activerecord / test / cases / attribute_methods_test.rb
1 require "cases/helper"
2 require 'models/topic'
3 require 'models/minimalistic'
4
5 class AttributeMethodsTest < ActiveRecord::TestCase
6 fixtures :topics
7 def setup
8 @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup
9 @target = Class.new(ActiveRecord::Base)
10 @target.table_name = 'topics'
11 end
12
13 def teardown
14 ActiveRecord::Base.send(:attribute_method_suffixes).clear
15 ActiveRecord::Base.attribute_method_suffix *@old_suffixes
16 end
17
18 def test_match_attribute_method_query_returns_match_data
19 assert_not_nil md = @target.match_attribute_method?('title=')
20 assert_equal 'title', md.pre_match
21 assert_equal ['='], md.captures
22
23 %w(_hello_world ist! _maybe?).each do |suffix|
24 @target.class_eval "def attribute#{suffix}(*args) args end"
25 @target.attribute_method_suffix suffix
26
27 assert_not_nil md = @target.match_attribute_method?("title#{suffix}")
28 assert_equal 'title', md.pre_match
29 assert_equal [suffix], md.captures
30 end
31 end
32
33 def test_declared_attribute_method_affects_respond_to_and_method_missing
34 topic = @target.new(:title => 'Budget')
35 assert topic.respond_to?('title')
36 assert_equal 'Budget', topic.title
37 assert !topic.respond_to?('title_hello_world')
38 assert_raise(NoMethodError) { topic.title_hello_world }
39
40 %w(_hello_world _it! _candidate= able?).each do |suffix|
41 @target.class_eval "def attribute#{suffix}(*args) args end"
42 @target.attribute_method_suffix suffix
43
44 meth = "title#{suffix}"
45 assert topic.respond_to?(meth)
46 assert_equal ['title'], topic.send(meth)
47 assert_equal ['title', 'a'], topic.send(meth, 'a')
48 assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3)
49 end
50 end
51
52 def test_should_unserialize_attributes_for_frozen_records
53 myobj = {:value1 => :value2}
54 topic = Topic.create("content" => myobj)
55 topic.freeze
56 assert_equal myobj, topic.content
57 end
58
59 def test_typecast_attribute_from_select_to_false
60 topic = Topic.create(:title => 'Budget')
61 topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test")
62 assert !topic.is_test?
63 end
64
65 def test_typecast_attribute_from_select_to_true
66 topic = Topic.create(:title => 'Budget')
67 topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test")
68 assert topic.is_test?
69 end
70
71 def test_kernel_methods_not_implemented_in_activerecord
72 %w(test name display y).each do |method|
73 assert !ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined"
74 end
75 end
76
77 def test_primary_key_implemented
78 assert Class.new(ActiveRecord::Base).instance_method_already_implemented?('id')
79 end
80
81 def test_defined_kernel_methods_implemented_in_model
82 %w(test name display y).each do |method|
83 klass = Class.new ActiveRecord::Base
84 klass.class_eval "def #{method}() 'defined #{method}' end"
85 assert klass.instance_method_already_implemented?(method), "##{method} is not defined"
86 end
87 end
88
89 def test_defined_kernel_methods_implemented_in_model_abstract_subclass
90 %w(test name display y).each do |method|
91 abstract = Class.new ActiveRecord::Base
92 abstract.class_eval "def #{method}() 'defined #{method}' end"
93 abstract.abstract_class = true
94 klass = Class.new abstract
95 assert klass.instance_method_already_implemented?(method), "##{method} is not defined"
96 end
97 end
98
99 def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model
100 %w(save create_or_update).each do |method|
101 klass = Class.new ActiveRecord::Base
102 klass.class_eval "def #{method}() 'defined #{method}' end"
103 assert_raise ActiveRecord::DangerousAttributeError do
104 klass.instance_method_already_implemented?(method)
105 end
106 end
107 end
108
109 def test_only_time_related_columns_are_meant_to_be_cached_by_default
110 expected = %w(datetime timestamp time date).sort
111 assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort
112 end
113
114 def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default
115 default_attributes = Topic.cached_attributes
116 Topic.cache_attributes :replies_count
117 expected = default_attributes + ["replies_count"]
118 assert_equal expected.sort, Topic.cached_attributes.sort
119 Topic.instance_variable_set "@cached_attributes", nil
120 end
121
122 def test_time_related_columns_are_actually_cached
123 column_types = %w(datetime timestamp time date).map(&:to_sym)
124 column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name)
125
126 assert_equal column_names.sort, Topic.cached_attributes.sort
127 assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort
128 end
129
130 def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else
131 t = topics(:first)
132 cache = t.instance_variable_get "@attributes_cache"
133
134 assert_not_nil cache
135 assert cache.empty?
136
137 all_columns = Topic.columns.map(&:name)
138 cached_columns = time_related_columns_on_topic
139 uncached_columns = all_columns - cached_columns
140
141 all_columns.each do |attr_name|
142 attribute_gets_cached = Topic.cache_attribute?(attr_name)
143 val = t.send attr_name unless attr_name == "type"
144 if attribute_gets_cached
145 assert cached_columns.include?(attr_name)
146 assert_equal val, cache[attr_name]
147 else
148 assert uncached_columns.include?(attr_name)
149 assert !cache.include?(attr_name)
150 end
151 end
152 end
153
154 def test_time_attributes_are_retrieved_in_current_time_zone
155 in_time_zone "Pacific Time (US & Canada)" do
156 utc_time = Time.utc(2008, 1, 1)
157 record = @target.new
158 record[:written_on] = utc_time
159 assert_equal utc_time, record.written_on # record.written on is equal to (i.e., simultaneous with) utc_time
160 assert_kind_of ActiveSupport::TimeWithZone, record.written_on # but is a TimeWithZone
161 assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone
162 assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time # and represents time values adjusted accordingly
163 end
164 end
165
166 def test_setting_time_zone_aware_attribute_to_utc
167 in_time_zone "Pacific Time (US & Canada)" do
168 utc_time = Time.utc(2008, 1, 1)
169 record = @target.new
170 record.written_on = utc_time
171 assert_equal utc_time, record.written_on
172 assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
173 assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
174 end
175 end
176
177 def test_setting_time_zone_aware_attribute_in_other_time_zone
178 utc_time = Time.utc(2008, 1, 1)
179 cst_time = utc_time.in_time_zone("Central Time (US & Canada)")
180 in_time_zone "Pacific Time (US & Canada)" do
181 record = @target.new
182 record.written_on = cst_time
183 assert_equal utc_time, record.written_on
184 assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
185 assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
186 end
187 end
188
189 def test_setting_time_zone_aware_attribute_with_string
190 utc_time = Time.utc(2008, 1, 1)
191 (-11..13).each do |timezone_offset|
192 time_string = utc_time.in_time_zone(timezone_offset).to_s
193 in_time_zone "Pacific Time (US & Canada)" do
194 record = @target.new
195 record.written_on = time_string
196 assert_equal Time.zone.parse(time_string), record.written_on
197 assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
198 assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
199 end
200 end
201 end
202
203 def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil
204 in_time_zone "Pacific Time (US & Canada)" do
205 record = @target.new
206 record.written_on = ' '
207 assert_nil record.written_on
208 end
209 end
210
211 def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone
212 time_string = 'Tue Jan 01 00:00:00 2008'
213 (-11..13).each do |timezone_offset|
214 in_time_zone timezone_offset do
215 record = @target.new
216 record.written_on = time_string
217 assert_equal Time.zone.parse(time_string), record.written_on
218 assert_equal ActiveSupport::TimeZone[timezone_offset], record.written_on.time_zone
219 assert_equal Time.utc(2008, 1, 1), record.written_on.time
220 end
221 end
222 end
223
224 def test_setting_time_zone_aware_attribute_in_current_time_zone
225 utc_time = Time.utc(2008, 1, 1)
226 in_time_zone "Pacific Time (US & Canada)" do
227 record = @target.new
228 record.written_on = utc_time.in_time_zone
229 assert_equal utc_time, record.written_on
230 assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
231 assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
232 end
233 end
234
235 def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
236 Topic.skip_time_zone_conversion_for_attributes = [:field_a]
237 Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
238
239 assert_equal [:field_a], Topic.skip_time_zone_conversion_for_attributes
240 assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes
241 end
242
243 def test_read_attributes_respect_access_control
244 privatize("title")
245
246 topic = @target.new(:title => "The pros and cons of programming naked.")
247 assert !topic.respond_to?(:title)
248 exception = assert_raise(NoMethodError) { topic.title }
249 assert_equal "Attempt to call private method", exception.message
250 assert_equal "I'm private", topic.send(:title)
251 end
252
253 def test_write_attributes_respect_access_control
254 privatize("title=(value)")
255
256 topic = @target.new
257 assert !topic.respond_to?(:title=)
258 exception = assert_raise(NoMethodError) { topic.title = "Pants"}
259 assert_equal "Attempt to call private method", exception.message
260 topic.send(:title=, "Very large pants")
261 end
262
263 def test_question_attributes_respect_access_control
264 privatize("title?")
265
266 topic = @target.new(:title => "Isaac Newton's pants")
267 assert !topic.respond_to?(:title?)
268 exception = assert_raise(NoMethodError) { topic.title? }
269 assert_equal "Attempt to call private method", exception.message
270 assert topic.send(:title?)
271 end
272
273 def test_bulk_update_respects_access_control
274 privatize("title=(value)")
275
276 assert_raise(ActiveRecord::UnknownAttributeError) { topic = @target.new(:title => "Rants about pants") }
277 assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
278 end
279
280 private
281 def time_related_columns_on_topic
282 Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name)
283 end
284
285 def in_time_zone(zone)
286 old_zone = Time.zone
287 old_tz = ActiveRecord::Base.time_zone_aware_attributes
288
289 Time.zone = zone ? ActiveSupport::TimeZone[zone] : nil
290 ActiveRecord::Base.time_zone_aware_attributes = !zone.nil?
291 yield
292 ensure
293 Time.zone = old_zone
294 ActiveRecord::Base.time_zone_aware_attributes = old_tz
295 end
296
297 def privatize(method_signature)
298 @target.class_eval <<-private_method
299 private
300 def #{method_signature}
301 "I'm private"
302 end
303 private_method
304 end
305 end