##// END OF EJS Templates
set_table_name and set_locking_column are deprecated....
Jean-Philippe Lang -
r9367:d940797aa79f
parent child
Show More
@@ -1,95 +1,95
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Principal < ActiveRecord::Base
19 set_table_name "#{table_name_prefix}users#{table_name_suffix}"
19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
20 20
21 21 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
22 22 has_many :memberships, :class_name => 'Member', :foreign_key => 'user_id', :include => [ :project, :roles ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name"
23 23 has_many :projects, :through => :memberships
24 24 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
25 25
26 26 # Groups and active users
27 27 scope :active, :conditions => "#{Principal.table_name}.status = 1"
28 28
29 29 scope :like, lambda {|q|
30 30 if q.blank?
31 31 {}
32 32 else
33 33 q = q.to_s.downcase
34 34 pattern = "%#{q}%"
35 35 sql = "LOWER(login) LIKE :p OR LOWER(firstname) LIKE :p OR LOWER(lastname) LIKE :p OR LOWER(mail) LIKE :p"
36 36 params = {:p => pattern}
37 37 if q =~ /^(.+)\s+(.+)$/
38 38 a, b = "#{$1}%", "#{$2}%"
39 39 sql << " OR (LOWER(firstname) LIKE :a AND LOWER(lastname) LIKE :b) OR (LOWER(firstname) LIKE :b AND LOWER(lastname) LIKE :a)"
40 40 params.merge!(:a => a, :b => b)
41 41 end
42 42 {:conditions => [sql, params]}
43 43 end
44 44 }
45 45
46 46 # Principals that are members of a collection of projects
47 47 scope :member_of, lambda {|projects|
48 48 projects = [projects] unless projects.is_a?(Array)
49 49 if projects.empty?
50 50 {:conditions => "1=0"}
51 51 else
52 52 ids = projects.map(&:id)
53 53 {:conditions => ["#{Principal.table_name}.status = 1 AND #{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
54 54 end
55 55 }
56 56 # Principals that are not members of projects
57 57 scope :not_member_of, lambda {|projects|
58 58 projects = [projects] unless projects.is_a?(Array)
59 59 if projects.empty?
60 60 {:conditions => "1=0"}
61 61 else
62 62 ids = projects.map(&:id)
63 63 {:conditions => ["#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids]}
64 64 end
65 65 }
66 66
67 67 before_create :set_default_empty_values
68 68
69 69 def name(formatter = nil)
70 70 to_s
71 71 end
72 72
73 73 def <=>(principal)
74 74 if principal.nil?
75 75 -1
76 76 elsif self.class.name == principal.class.name
77 77 self.to_s.downcase <=> principal.to_s.downcase
78 78 else
79 79 # groups after users
80 80 principal.class.name <=> self.class.name
81 81 end
82 82 end
83 83
84 84 protected
85 85
86 86 # Make sure we don't try to insert NULL values (see #4632)
87 87 def set_default_empty_values
88 88 self.login ||= ''
89 89 self.hashed_password ||= ''
90 90 self.firstname ||= ''
91 91 self.lastname ||= ''
92 92 self.mail ||= ''
93 93 true
94 94 end
95 95 end
@@ -1,124 +1,124
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'zlib'
19 19
20 20 class WikiContent < ActiveRecord::Base
21 set_locking_column :version
21 self.locking_column = 'version'
22 22 belongs_to :page, :class_name => 'WikiPage', :foreign_key => 'page_id'
23 23 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
24 24 validates_presence_of :text
25 25 validates_length_of :comments, :maximum => 255, :allow_nil => true
26 26
27 27 acts_as_versioned
28 28
29 29 def visible?(user=User.current)
30 30 page.visible?(user)
31 31 end
32 32
33 33 def project
34 34 page.project
35 35 end
36 36
37 37 def attachments
38 38 page.nil? ? [] : page.attachments
39 39 end
40 40
41 41 # Returns the mail adresses of users that should be notified
42 42 def recipients
43 43 notified = project.notified_users
44 44 notified.reject! {|user| !visible?(user)}
45 45 notified.collect(&:mail)
46 46 end
47 47
48 48 # Return true if the content is the current page content
49 49 def current_version?
50 50 true
51 51 end
52 52
53 53 class Version
54 54 belongs_to :page, :class_name => '::WikiPage', :foreign_key => 'page_id'
55 55 belongs_to :author, :class_name => '::User', :foreign_key => 'author_id'
56 56 attr_protected :data
57 57
58 58 acts_as_event :title => Proc.new {|o| "#{l(:label_wiki_edit)}: #{o.page.title} (##{o.version})"},
59 59 :description => :comments,
60 60 :datetime => :updated_on,
61 61 :type => 'wiki-page',
62 62 :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.page.wiki.project, :id => o.page.title, :version => o.version}}
63 63
64 64 acts_as_activity_provider :type => 'wiki_edits',
65 65 :timestamp => "#{WikiContent.versioned_table_name}.updated_on",
66 66 :author_key => "#{WikiContent.versioned_table_name}.author_id",
67 67 :permission => :view_wiki_edits,
68 68 :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
69 69 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
70 70 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
71 71 "#{WikiContent.versioned_table_name}.id",
72 72 :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
73 73 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " +
74 74 "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"}
75 75
76 76 def text=(plain)
77 77 case Setting.wiki_compression
78 78 when 'gzip'
79 79 begin
80 80 self.data = Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION)
81 81 self.compression = 'gzip'
82 82 rescue
83 83 self.data = plain
84 84 self.compression = ''
85 85 end
86 86 else
87 87 self.data = plain
88 88 self.compression = ''
89 89 end
90 90 plain
91 91 end
92 92
93 93 def text
94 94 @text ||= begin
95 95 str = case compression
96 96 when 'gzip'
97 97 Zlib::Inflate.inflate(data)
98 98 else
99 99 # uncompressed data
100 100 data
101 101 end
102 102 str.force_encoding("UTF-8") if str.respond_to?(:force_encoding)
103 103 str
104 104 end
105 105 end
106 106
107 107 def project
108 108 page.project
109 109 end
110 110
111 111 # Return true if the content is the current page content
112 112 def current_version?
113 113 page.content.version == self.version
114 114 end
115 115
116 116 # Returns the previous version or nil
117 117 def previous
118 118 @previous ||= WikiContent::Version.find(:first,
119 119 :order => 'version DESC',
120 120 :include => :author,
121 121 :conditions => ["wiki_content_id = ? AND version < ?", wiki_content_id, version])
122 122 end
123 123 end
124 124 end
@@ -1,566 +1,566
1 1 # Copyright (c) 2005 Rick Olson
2 2 #
3 3 # Permission is hereby granted, free of charge, to any person obtaining
4 4 # a copy of this software and associated documentation files (the
5 5 # "Software"), to deal in the Software without restriction, including
6 6 # without limitation the rights to use, copy, modify, merge, publish,
7 7 # distribute, sublicense, and/or sell copies of the Software, and to
8 8 # permit persons to whom the Software is furnished to do so, subject to
9 9 # the following conditions:
10 10 #
11 11 # The above copyright notice and this permission notice shall be
12 12 # included in all copies or substantial portions of the Software.
13 13 #
14 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 18 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 20 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 21
22 22 module ActiveRecord #:nodoc:
23 23 module Acts #:nodoc:
24 24 # Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
25 25 # versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
26 26 # column is present as well.
27 27 #
28 28 # The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
29 29 # your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
30 30 #
31 31 # class Page < ActiveRecord::Base
32 32 # # assumes pages_versions table
33 33 # acts_as_versioned
34 34 # end
35 35 #
36 36 # Example:
37 37 #
38 38 # page = Page.create(:title => 'hello world!')
39 39 # page.version # => 1
40 40 #
41 41 # page.title = 'hello world'
42 42 # page.save
43 43 # page.version # => 2
44 44 # page.versions.size # => 2
45 45 #
46 46 # page.revert_to(1) # using version number
47 47 # page.title # => 'hello world!'
48 48 #
49 49 # page.revert_to(page.versions.last) # using versioned instance
50 50 # page.title # => 'hello world'
51 51 #
52 52 # page.versions.earliest # efficient query to find the first version
53 53 # page.versions.latest # efficient query to find the most recently created version
54 54 #
55 55 #
56 56 # Simple Queries to page between versions
57 57 #
58 58 # page.versions.before(version)
59 59 # page.versions.after(version)
60 60 #
61 61 # Access the previous/next versions from the versioned model itself
62 62 #
63 63 # version = page.versions.latest
64 64 # version.previous # go back one version
65 65 # version.next # go forward one version
66 66 #
67 67 # See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
68 68 module Versioned
69 69 CALLBACKS = [:set_new_version, :save_version_on_create, :save_version?, :clear_altered_attributes]
70 70 def self.included(base) # :nodoc:
71 71 base.extend ClassMethods
72 72 end
73 73
74 74 module ClassMethods
75 75 # == Configuration options
76 76 #
77 77 # * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
78 78 # * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
79 79 # * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
80 80 # * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
81 81 # * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
82 82 # * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
83 83 # * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
84 84 # * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
85 85 # For finer control, pass either a Proc or modify Model#version_condition_met?
86 86 #
87 87 # acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
88 88 #
89 89 # or...
90 90 #
91 91 # class Auction
92 92 # def version_condition_met? # totally bypasses the <tt>:if</tt> option
93 93 # !expired?
94 94 # end
95 95 # end
96 96 #
97 97 # * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
98 98 # either a symbol or array of symbols. WARNING - This will attempt to overwrite any attribute setters you may have.
99 99 # Use this instead if you want to write your own attribute setters (and ignore if_changed):
100 100 #
101 101 # def name=(new_name)
102 102 # write_changed_attribute :name, new_name
103 103 # end
104 104 #
105 105 # * <tt>extend</tt> - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block
106 106 # to create an anonymous mixin:
107 107 #
108 108 # class Auction
109 109 # acts_as_versioned do
110 110 # def started?
111 111 # !started_at.nil?
112 112 # end
113 113 # end
114 114 # end
115 115 #
116 116 # or...
117 117 #
118 118 # module AuctionExtension
119 119 # def started?
120 120 # !started_at.nil?
121 121 # end
122 122 # end
123 123 # class Auction
124 124 # acts_as_versioned :extend => AuctionExtension
125 125 # end
126 126 #
127 127 # Example code:
128 128 #
129 129 # @auction = Auction.find(1)
130 130 # @auction.started?
131 131 # @auction.versions.first.started?
132 132 #
133 133 # == Database Schema
134 134 #
135 135 # The model that you're versioning needs to have a 'version' attribute. The model is versioned
136 136 # into a table called #{model}_versions where the model name is singlular. The _versions table should
137 137 # contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
138 138 #
139 139 # A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
140 140 # then that field is reflected in the versioned model as 'versioned_type' by default.
141 141 #
142 142 # Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
143 143 # method, perfect for a migration. It will also create the version column if the main model does not already have it.
144 144 #
145 145 # class AddVersions < ActiveRecord::Migration
146 146 # def self.up
147 147 # # create_versioned_table takes the same options hash
148 148 # # that create_table does
149 149 # Post.create_versioned_table
150 150 # end
151 151 #
152 152 # def self.down
153 153 # Post.drop_versioned_table
154 154 # end
155 155 # end
156 156 #
157 157 # == Changing What Fields Are Versioned
158 158 #
159 159 # By default, acts_as_versioned will version all but these fields:
160 160 #
161 161 # [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
162 162 #
163 163 # You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
164 164 #
165 165 # class Post < ActiveRecord::Base
166 166 # acts_as_versioned
167 167 # self.non_versioned_columns << 'comments_count'
168 168 # end
169 169 #
170 170 def acts_as_versioned(options = {}, &extension)
171 171 # don't allow multiple calls
172 172 return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
173 173
174 174 send :include, ActiveRecord::Acts::Versioned::ActMethods
175 175
176 176 cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
177 177 :version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
178 178 :version_association_options
179 179
180 180 # legacy
181 181 alias_method :non_versioned_fields, :non_versioned_columns
182 182 alias_method :non_versioned_fields=, :non_versioned_columns=
183 183
184 184 class << self
185 185 alias_method :non_versioned_fields, :non_versioned_columns
186 186 alias_method :non_versioned_fields=, :non_versioned_columns=
187 187 end
188 188
189 189 send :attr_accessor, :altered_attributes
190 190
191 191 self.versioned_class_name = options[:class_name] || "Version"
192 192 self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
193 193 self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
194 194 self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
195 195 self.version_column = options[:version_column] || 'version'
196 196 self.version_sequence_name = options[:sequence_name]
197 197 self.max_version_limit = options[:limit].to_i
198 198 self.version_condition = options[:if] || true
199 199 self.non_versioned_columns = [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
200 200 self.version_association_options = {
201 201 :class_name => "#{self.to_s}::#{versioned_class_name}",
202 202 :foreign_key => versioned_foreign_key,
203 203 :dependent => :delete_all
204 204 }.merge(options[:association_options] || {})
205 205
206 206 if block_given?
207 207 extension_module_name = "#{versioned_class_name}Extension"
208 208 silence_warnings do
209 209 self.const_set(extension_module_name, Module.new(&extension))
210 210 end
211 211
212 212 options[:extend] = self.const_get(extension_module_name)
213 213 end
214 214
215 215 class_eval do
216 216 has_many :versions, version_association_options do
217 217 # finds earliest version of this record
218 218 def earliest
219 219 @earliest ||= find(:first, :order => 'version')
220 220 end
221 221
222 222 # find latest version of this record
223 223 def latest
224 224 @latest ||= find(:first, :order => 'version desc')
225 225 end
226 226 end
227 227 before_save :set_new_version
228 228 after_create :save_version_on_create
229 229 after_update :save_version
230 230 after_save :clear_old_versions
231 231 after_save :clear_altered_attributes
232 232
233 233 unless options[:if_changed].nil?
234 234 self.track_altered_attributes = true
235 235 options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
236 236 options[:if_changed].each do |attr_name|
237 237 define_method("#{attr_name}=") do |value|
238 238 write_changed_attribute attr_name, value
239 239 end
240 240 end
241 241 end
242 242
243 243 include options[:extend] if options[:extend].is_a?(Module)
244 244 end
245 245
246 246 # create the dynamic versioned model
247 247 const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
248 248 def self.reloadable? ; false ; end
249 249 # find first version before the given version
250 250 def self.before(version)
251 251 find :first, :order => 'version desc',
252 252 :conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
253 253 end
254 254
255 255 # find first version after the given version.
256 256 def self.after(version)
257 257 find :first, :order => 'version',
258 258 :conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
259 259 end
260 260
261 261 def previous
262 262 self.class.before(self)
263 263 end
264 264
265 265 def next
266 266 self.class.after(self)
267 267 end
268 268
269 269 def versions_count
270 270 page.version
271 271 end
272 272 end
273 273
274 274 versioned_class.cattr_accessor :original_class
275 275 versioned_class.original_class = self
276 versioned_class.set_table_name versioned_table_name
276 versioned_class.table_name = versioned_table_name
277 277 versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
278 278 :class_name => "::#{self.to_s}",
279 279 :foreign_key => versioned_foreign_key
280 280 versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
281 281 versioned_class.set_sequence_name version_sequence_name if version_sequence_name
282 282 end
283 283 end
284 284
285 285 module ActMethods
286 286 def self.included(base) # :nodoc:
287 287 base.extend ClassMethods
288 288 end
289 289
290 290 # Finds a specific version of this record
291 291 def find_version(version = nil)
292 292 self.class.find_version(id, version)
293 293 end
294 294
295 295 # Saves a version of the model if applicable
296 296 def save_version
297 297 save_version_on_create if save_version?
298 298 end
299 299
300 300 # Saves a version of the model in the versioned table. This is called in the after_save callback by default
301 301 def save_version_on_create
302 302 rev = self.class.versioned_class.new
303 303 self.clone_versioned_model(self, rev)
304 304 rev.version = send(self.class.version_column)
305 305 rev.send("#{self.class.versioned_foreign_key}=", self.id)
306 306 rev.save
307 307 end
308 308
309 309 # Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
310 310 # Override this method to set your own criteria for clearing old versions.
311 311 def clear_old_versions
312 312 return if self.class.max_version_limit == 0
313 313 excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
314 314 if excess_baggage > 0
315 315 sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
316 316 self.class.versioned_class.connection.execute sql
317 317 end
318 318 end
319 319
320 320 def versions_count
321 321 version
322 322 end
323 323
324 324 # Reverts a model to a given version. Takes either a version number or an instance of the versioned model
325 325 def revert_to(version)
326 326 if version.is_a?(self.class.versioned_class)
327 327 return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
328 328 else
329 329 return false unless version = versions.find_by_version(version)
330 330 end
331 331 self.clone_versioned_model(version, self)
332 332 self.send("#{self.class.version_column}=", version.version)
333 333 true
334 334 end
335 335
336 336 # Reverts a model to a given version and saves the model.
337 337 # Takes either a version number or an instance of the versioned model
338 338 def revert_to!(version)
339 339 revert_to(version) ? save_without_revision : false
340 340 end
341 341
342 342 # Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
343 343 def save_without_revision
344 344 save_without_revision!
345 345 true
346 346 rescue
347 347 false
348 348 end
349 349
350 350 def save_without_revision!
351 351 without_locking do
352 352 without_revision do
353 353 save!
354 354 end
355 355 end
356 356 end
357 357
358 358 # Returns an array of attribute keys that are versioned. See non_versioned_columns
359 359 def versioned_attributes
360 360 self.attributes.keys.select { |k| !self.class.non_versioned_columns.include?(k) }
361 361 end
362 362
363 363 # If called with no parameters, gets whether the current model has changed and needs to be versioned.
364 364 # If called with a single parameter, gets whether the parameter has changed.
365 365 def changed?(attr_name = nil)
366 366 attr_name.nil? ?
367 367 (!self.class.track_altered_attributes || (altered_attributes && altered_attributes.length > 0)) :
368 368 (altered_attributes && altered_attributes.include?(attr_name.to_s))
369 369 end
370 370
371 371 # keep old dirty? method
372 372 alias_method :dirty?, :changed?
373 373
374 374 # Clones a model. Used when saving a new version or reverting a model's version.
375 375 def clone_versioned_model(orig_model, new_model)
376 376 self.versioned_attributes.each do |key|
377 377 new_model.send("#{key}=", orig_model.send(key)) if orig_model.has_attribute?(key)
378 378 end
379 379
380 380 if self.class.columns_hash.include?(self.class.inheritance_column)
381 381 if orig_model.is_a?(self.class.versioned_class)
382 382 new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
383 383 elsif new_model.is_a?(self.class.versioned_class)
384 384 new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
385 385 end
386 386 end
387 387 end
388 388
389 389 # Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
390 390 def save_version?
391 391 version_condition_met? && changed?
392 392 end
393 393
394 394 # Checks condition set in the :if option to check whether a revision should be created or not. Override this for
395 395 # custom version condition checking.
396 396 def version_condition_met?
397 397 case
398 398 when version_condition.is_a?(Symbol)
399 399 send(version_condition)
400 400 when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
401 401 version_condition.call(self)
402 402 else
403 403 version_condition
404 404 end
405 405 end
406 406
407 407 # Executes the block with the versioning callbacks disabled.
408 408 #
409 409 # @foo.without_revision do
410 410 # @foo.save
411 411 # end
412 412 #
413 413 def without_revision(&block)
414 414 self.class.without_revision(&block)
415 415 end
416 416
417 417 # Turns off optimistic locking for the duration of the block
418 418 #
419 419 # @foo.without_locking do
420 420 # @foo.save
421 421 # end
422 422 #
423 423 def without_locking(&block)
424 424 self.class.without_locking(&block)
425 425 end
426 426
427 427 def empty_callback() end #:nodoc:
428 428
429 429 protected
430 430 # sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
431 431 def set_new_version
432 432 self.send("#{self.class.version_column}=", self.next_version) if new_record? || (!locking_enabled? && save_version?)
433 433 end
434 434
435 435 # Gets the next available version for the current record, or 1 for a new record
436 436 def next_version
437 437 return 1 if new_record?
438 438 (versions.calculate(:max, :version) || 0) + 1
439 439 end
440 440
441 441 # clears current changed attributes. Called after save.
442 442 def clear_altered_attributes
443 443 self.altered_attributes = []
444 444 end
445 445
446 446 def write_changed_attribute(attr_name, attr_value)
447 447 # Convert to db type for comparison. Avoids failing Float<=>String comparisons.
448 448 attr_value_for_db = self.class.columns_hash[attr_name.to_s].type_cast(attr_value)
449 449 (self.altered_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) || self.send(attr_name) == attr_value_for_db
450 450 write_attribute(attr_name, attr_value_for_db)
451 451 end
452 452
453 453 module ClassMethods
454 454 # Finds a specific version of a specific row of this model
455 455 def find_version(id, version = nil)
456 456 return find(id) unless version
457 457
458 458 conditions = ["#{versioned_foreign_key} = ? AND version = ?", id, version]
459 459 options = { :conditions => conditions, :limit => 1 }
460 460
461 461 if result = find_versions(id, options).first
462 462 result
463 463 else
464 464 raise RecordNotFound, "Couldn't find #{name} with ID=#{id} and VERSION=#{version}"
465 465 end
466 466 end
467 467
468 468 # Finds versions of a specific model. Takes an options hash like <tt>find</tt>
469 469 def find_versions(id, options = {})
470 470 versioned_class.find :all, {
471 471 :conditions => ["#{versioned_foreign_key} = ?", id],
472 472 :order => 'version' }.merge(options)
473 473 end
474 474
475 475 # Returns an array of columns that are versioned. See non_versioned_columns
476 476 def versioned_columns
477 477 self.columns.select { |c| !non_versioned_columns.include?(c.name) }
478 478 end
479 479
480 480 # Returns an instance of the dynamic versioned model
481 481 def versioned_class
482 482 const_get versioned_class_name
483 483 end
484 484
485 485 # Rake migration task to create the versioned table using options passed to acts_as_versioned
486 486 def create_versioned_table(create_table_options = {})
487 487 # create version column in main table if it does not exist
488 488 if !self.content_columns.find { |c| %w(version lock_version).include? c.name }
489 489 self.connection.add_column table_name, :version, :integer
490 490 end
491 491
492 492 self.connection.create_table(versioned_table_name, create_table_options) do |t|
493 493 t.column versioned_foreign_key, :integer
494 494 t.column :version, :integer
495 495 end
496 496
497 497 updated_col = nil
498 498 self.versioned_columns.each do |col|
499 499 updated_col = col if !updated_col && %(updated_at updated_on).include?(col.name)
500 500 self.connection.add_column versioned_table_name, col.name, col.type,
501 501 :limit => col.limit,
502 502 :default => col.default,
503 503 :scale => col.scale,
504 504 :precision => col.precision
505 505 end
506 506
507 507 if type_col = self.columns_hash[inheritance_column]
508 508 self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
509 509 :limit => type_col.limit,
510 510 :default => type_col.default,
511 511 :scale => type_col.scale,
512 512 :precision => type_col.precision
513 513 end
514 514
515 515 if updated_col.nil?
516 516 self.connection.add_column versioned_table_name, :updated_at, :timestamp
517 517 end
518 518 end
519 519
520 520 # Rake migration task to drop the versioned table
521 521 def drop_versioned_table
522 522 self.connection.drop_table versioned_table_name
523 523 end
524 524
525 525 # Executes the block with the versioning callbacks disabled.
526 526 #
527 527 # Foo.without_revision do
528 528 # @foo.save
529 529 # end
530 530 #
531 531 def without_revision(&block)
532 532 class_eval do
533 533 CALLBACKS.each do |attr_name|
534 534 alias_method "orig_#{attr_name}".to_sym, attr_name
535 535 alias_method attr_name, :empty_callback
536 536 end
537 537 end
538 538 block.call
539 539 ensure
540 540 class_eval do
541 541 CALLBACKS.each do |attr_name|
542 542 alias_method attr_name, "orig_#{attr_name}".to_sym
543 543 end
544 544 end
545 545 end
546 546
547 547 # Turns off optimistic locking for the duration of the block
548 548 #
549 549 # Foo.without_locking do
550 550 # @foo.save
551 551 # end
552 552 #
553 553 def without_locking(&block)
554 554 current = ActiveRecord::Base.lock_optimistically
555 555 ActiveRecord::Base.lock_optimistically = false if current
556 556 result = block.call
557 557 ActiveRecord::Base.lock_optimistically = true if current
558 558 result
559 559 end
560 560 end
561 561 end
562 562 end
563 563 end
564 564 end
565 565
566 566 ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned No newline at end of file
@@ -1,9 +1,9
1 1 module OpenIdAuthentication
2 2 class Association < ActiveRecord::Base
3 set_table_name :open_id_authentication_associations
3 self.table_name = :open_id_authentication_associations
4 4
5 5 def from_record
6 6 OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
7 7 end
8 8 end
9 9 end
@@ -1,5 +1,5
1 1 module OpenIdAuthentication
2 2 class Nonce < ActiveRecord::Base
3 set_table_name :open_id_authentication_nonces
3 self.table_name = :open_id_authentication_nonces
4 4 end
5 5 end
General Comments 0
You need to be logged in to leave comments. Login now