public
Fork of rails/rails
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/josh/rails.git
Add attr_readonly to specify columns that are skipped during a normal 
ActiveRecord #save operation. Closes #6896 [dcmanges]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7693 
5ecf4fe2-1ee6-0310-87b1-e25e094e27de
technoweenie (author)
Sun Sep 30 00:09:44 -0700 2007
commit  66d05f5e2c7ac6b18220956fbcf34efcd32638fc
tree    6544dd694d02af7438489a4864daec9c2bdce0b5
parent  30a652ad41428b922b1ed637f491776f1f1dff13
...
1
2
 
 
 
 
 
 
 
 
 
 
 
3
4
5
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0
@@ -1,5 +1,16 @@
0
 *2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.14.2 - 1.15.3]
0
 
0
+* Add attr_readonly to specify columns that are skipped during a normal ActiveRecord #save operation. Closes #6896 [dcmanges]
0
+
0
+ class Comment < ActiveRecord::Base
0
+ # Automatically sets Article#comments_count as readonly.
0
+ belongs_to :article, :counter_cache => :comments_count
0
+ end
0
+
0
+ class Article < ActiveRecord::Base
0
+ attr_readonly :approved_comments_count
0
+ end
0
+
0
 * Make size for has_many :through use counter cache if it exists. Closes #9734 [xaviershay]
0
 
0
 * Remove DB2 adapter since IBM chooses to maintain their own adapter instead. [Jeremy Kemper]
...
841
842
843
844
 
 
 
 
 
845
846
847
...
841
842
843
 
844
845
846
847
848
849
850
851
0
@@ -841,7 +841,11 @@ module ActiveRecord
0
           module_eval(
0
             "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
0
             " unless #{reflection.name}.nil?'"
0
- )
0
+ )
0
+
0
+ module_eval(
0
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name})"
0
+ )
0
         end
0
       end
0
 
...
636
637
638
 
 
 
 
 
 
 
 
 
639
640
641
...
1953
1954
1955
1956
 
1957
1958
1959
...
2008
2009
2010
 
 
 
 
 
 
 
 
 
2011
2012
2013
...
2018
2019
2020
2021
2022
 
 
2023
2024
2025
2026
2027
 
2028
2029
2030
...
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
...
1962
1963
1964
 
1965
1966
1967
1968
...
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
...
2036
2037
2038
 
 
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
0
@@ -636,6 +636,15 @@ module ActiveRecord #:nodoc:
0
         read_inheritable_attribute("attr_accessible")
0
       end
0
 
0
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
0
+ def attr_readonly(*attributes)
0
+ write_inheritable_array("attr_readonly", attributes - (readonly_attributes || []))
0
+ end
0
+
0
+ # Returns an array of all the attributes that have been specified as readonly.
0
+ def readonly_attributes
0
+ read_inheritable_attribute("attr_readonly")
0
+ end
0
 
0
       # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
0
       # then specify the name of that attribute using this method and it will be handled automatically.
0
@@ -1953,7 +1962,7 @@ module ActiveRecord #:nodoc:
0
       def update
0
         connection.update(
0
           "UPDATE #{self.class.table_name} " +
0
- "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
0
+ "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false))} " +
0
           "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
0
           "#{self.class.name} Update"
0
         )
0
@@ -2008,6 +2017,15 @@ module ActiveRecord #:nodoc:
0
           raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
0
         end
0
       end
0
+
0
+ # Removes attributes which have been marked as readonly.
0
+ def remove_readonly_attributes(attributes)
0
+ unless self.class.readonly_attributes.nil?
0
+ attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"").intern) }
0
+ else
0
+ attributes
0
+ end
0
+ end
0
 
0
       # The primary key and inheritance column can never be set by mass-assignment for security reasons.
0
       def attributes_protected_by_default
0
@@ -2018,13 +2036,14 @@ module ActiveRecord #:nodoc:
0
 
0
       # Returns copy of the attributes hash where all the values have been safely quoted for use in
0
       # an SQL statement.
0
- def attributes_with_quotes(include_primary_key = true)
0
- attributes.inject({}) do |quoted, (name, value)|
0
+ def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true)
0
+ quoted = attributes.inject({}) do |quoted, (name, value)|
0
           if column = column_for_attribute(name)
0
             quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary
0
           end
0
           quoted
0
         end
0
+ include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
0
       end
0
 
0
       # Quote strings appropriately for SQL statements.
...
1175
1176
1177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1178
1179
1180
...
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
0
@@ -1175,6 +1175,24 @@ class BelongsToAssociationsTest < Test::Unit::TestCase
0
     topic.update_attributes(:title => "37signals")
0
     assert_equal 1, Topic.find(topic.id)[:replies_count]
0
   end
0
+
0
+ def test_belongs_to_counter_after_save
0
+ topic = Topic.create("title" => "monday night")
0
+ topic.replies.create("title" => "re: monday night", "content" => "football")
0
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
0
+
0
+ topic.save
0
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
0
+ end
0
+
0
+ def test_belongs_to_counter_after_update_attributes
0
+ topic = Topic.create("title" => "37s")
0
+ topic.replies.create("title" => "re: 37s", "content" => "rails")
0
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
0
+
0
+ topic.update_attributes("title" => "37signals")
0
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
0
+ end
0
 
0
   def test_assignment_before_parent_saved
0
     client = Client.find(:first)
...
47
48
49
 
 
 
 
50
51
52
...
840
841
842
 
 
 
 
 
 
 
 
 
 
 
 
 
843
844
845
...
1222
1223
1224
1225
1226
1227
1228
1229
1230
 
 
 
 
 
 
1231
1232
1233
...
1237
1238
1239
1240
1241
1242
1243
1244
 
1245
1246
1247
 
 
 
 
 
1248
1249
1250
...
47
48
49
50
51
52
53
54
55
56
...
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
...
1239
1240
1241
 
 
 
 
 
 
1242
1243
1244
1245
1246
1247
1248
1249
1250
...
1254
1255
1256
 
 
 
 
 
1257
1258
 
 
1259
1260
1261
1262
1263
1264
1265
1266
0
@@ -47,6 +47,10 @@ class TightDescendant < TightPerson
0
   attr_accessible :phone_number
0
 end
0
 
0
+class ReadonlyTitlePost < Post
0
+ attr_readonly :title
0
+end
0
+
0
 class Booleantest < ActiveRecord::Base; end
0
 
0
 class Task < ActiveRecord::Base
0
@@ -840,6 +844,19 @@ class BasicsTest < Test::Unit::TestCase
0
     assert_nil TightDescendant.protected_attributes
0
     assert_equal [ :name, :address, :phone_number ], TightDescendant.accessible_attributes
0
   end
0
+
0
+ def test_readonly_attributes
0
+ assert_equal [ :title ], ReadonlyTitlePost.readonly_attributes
0
+
0
+ post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable")
0
+ post.reload
0
+ assert_equal "cannot change this", post.title
0
+
0
+ post.update_attributes(:title => "try to change", :body => "changed")
0
+ post.reload
0
+ assert_equal "cannot change this", post.title
0
+ assert_equal "changed", post.body
0
+ end
0
 
0
   def test_multiparameter_attributes_on_date
0
     attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
0
@@ -1222,12 +1239,12 @@ class BasicsTest < Test::Unit::TestCase
0
   end
0
 
0
   def test_increment_attribute
0
- assert_equal 1, topics(:first).replies_count
0
- topics(:first).increment! :replies_count
0
- assert_equal 2, topics(:first, :reload).replies_count
0
-
0
- topics(:first).increment(:replies_count).increment!(:replies_count)
0
- assert_equal 4, topics(:first, :reload).replies_count
0
+ assert_equal 50, accounts(:signals37).credit_limit
0
+ accounts(:signals37).increment! :credit_limit
0
+ assert_equal 51, accounts(:signals37, :reload).credit_limit
0
+
0
+ accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
0
+ assert_equal 53, accounts(:signals37, :reload).credit_limit
0
   end
0
   
0
   def test_increment_nil_attribute
0
@@ -1237,14 +1254,13 @@ class BasicsTest < Test::Unit::TestCase
0
   end
0
   
0
   def test_decrement_attribute
0
- topics(:first).increment(:replies_count).increment!(:replies_count)
0
- assert_equal 3, topics(:first).replies_count
0
-
0
- topics(:first).decrement!(:replies_count)
0
- assert_equal 2, topics(:first, :reload).replies_count
0
+ assert_equal 50, accounts(:signals37).credit_limit
0
 
0
- topics(:first).decrement(:replies_count).decrement!(:replies_count)
0
- assert_equal 0, topics(:first, :reload).replies_count
0
+ accounts(:signals37).decrement!(:credit_limit)
0
+ assert_equal 49, accounts(:signals37, :reload).credit_limit
0
+
0
+ accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
0
+ assert_equal 47, accounts(:signals37, :reload).credit_limit
0
   end
0
   
0
   def test_toggle_attribute

Comments

    No one has commented yet.