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