This repository has been archived by the owner on Jan 8, 2018. It is now read-only.
forked from chiliproject/chiliproject
/
acts_as_journalized.rb
190 lines (164 loc) · 7.47 KB
/
acts_as_journalized.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#-- encoding: UTF-8
# This file is part of the acts_as_journalized plugin for the redMine
# project management software
#
# Copyright (C) 2010 Finn GmbH, http://finn.de
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either journal 2
# of the License, or (at your option) any later journal.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Dir[File.expand_path("../redmine/acts/journalized/*.rb", __FILE__)].each{|f| require f }
require "ar_condition"
module Redmine
module Acts
module Journalized
def self.included(base)
base.extend ClassMethods
base.extend Versioned
end
module ClassMethods
attr_writer :journal_class_name
def journal_class_name
defined?(@journal_class_name) ? @journal_class_name : superclass.journal_class_name
end
def plural_name
self.name.underscore.pluralize
end
# A model might provide as many activity_types as it wishes.
# Activities are just different search options for the event a model provides
def acts_as_activity(options = {})
activity_hash = journalized_activity_hash(options)
type = activity_hash[:type]
acts_as_activity_provider activity_hash
unless Redmine::Activity.providers[type].include? self.name
Redmine::Activity.register type.to_sym, :class_name => self.name
end
end
# This call will add an activity and, if neccessary, start the journaling and
# add an event callback on the model.
# Versioning and acting as an Event may only be applied once.
# To apply more than on activity, use acts_as_activity
def acts_as_journalized(options = {}, &block)
activity_hash, event_hash, journal_hash = split_option_hashes(options)
self.journal_class_name = journal_hash.delete(:class_name) || "#{name.gsub("::", "_")}Journal"
acts_as_activity(activity_hash)
return if journaled?
include Options
include Changes
include Creation
include Users
include Reversion
include Reset
include Reload
include Permissions
include SaveHooks
include FormatHooks
# FIXME: When the transition to the new API is complete, remove me
include Deprecated
journal_class.acts_as_event journalized_event_hash(event_hash)
(journal_hash[:except] ||= []) << self.primary_key << inheritance_column <<
:updated_on << :updated_at << :lock_version << :lft << :rgt
prepare_journaled_options(journal_hash)
has_many :journals, journal_hash, &block
end
def journal_class
if Object.const_defined?(journal_class_name)
Object.const_get(journal_class_name)
else
Object.const_set(journal_class_name, Class.new(Journal)).tap do |c|
# Run after the inherited hook to associate with the parent record.
# This eager loads the associated project (for permissions) if possible
if project_assoc = reflect_on_association(:project).try(:name)
include_option = ", :include => :#{project_assoc.to_s}"
end
c.class_eval("belongs_to :journaled, :class_name => '#{name}' #{include_option}")
c.class_eval("belongs_to :#{name.gsub("::", "_").underscore},
:foreign_key => 'journaled_id' #{include_option}")
end
end
end
private
# Splits an option has into three hashes:
## => [{ options prefixed with "activity_" }, { options prefixed with "event_" }, { other options }]
def split_option_hashes(options)
activity_hash = {}
event_hash = {}
journal_hash = {}
options.each_pair do |k, v|
case
when k.to_s =~ /^activity_(.+)$/
activity_hash[$1.to_sym] = v
when k.to_s =~ /^event_(.+)$/
event_hash[$1.to_sym] = v
else
journal_hash[k.to_sym] = v
end
end
[activity_hash, event_hash, journal_hash]
end
# Merges the passed activity_hash with the options we require for
# acts_as_journalized to work, as follows:
# # type is the supplied or the pluralized class name
# # timestamp is supplied or the journal's created_at
# # author_key will always be the journal's author
# #
# # find_options are merged as follows:
# # # select statement is enriched with the journal fields
# # # journal association is added to the includes
# # # if a project is associated with the model, this is added to the includes
# # # the find conditions are extended to only choose journals which have the proper activity_type
# => a valid activity hash
def journalized_activity_hash(options)
options.tap do |h|
h[:type] ||= plural_name
h[:timestamp] ||= "#{journal_class.table_name}.created_at"
h[:author_key] ||= "#{journal_class.table_name}.user_id"
h[:find_options] ||= {} # in case it is nil
h[:find_options] = {}.tap do |opts|
cond = ::ARCondition.new
cond.add(["#{journal_class.table_name}.activity_type = ?", h[:type]])
cond.add(h[:find_options][:conditions]) if h[:find_options][:conditions]
opts[:conditions] = cond.conditions
include_opts = []
include_opts << :project if reflect_on_association(:project)
if h[:find_options][:include]
include_opts += case h[:find_options][:include]
when Array then h[:find_options][:include]
else [h[:find_options][:include]]
end
end
include_opts.uniq!
opts[:include] = [:journaled => include_opts]
#opts[:joins] = h[:find_options][:joins] if h[:find_options][:joins]
end
end
end
# Merges the event hashes defaults with the options provided by the user
# The defaults take their details from the journal
def journalized_event_hash(options)
unless options.has_key? :url
options[:url] = Proc.new do |journal|
{ :controller => plural_name,
:action => 'show',
:id => journal.journaled_id,
:anchor => ("note-#{journal.anchor}" unless journal.initial?) }
end
end
options[:type] ||= self.name.underscore.dasherize # Make sure the name of the journalized model and not the name of the journal is used for events
options[:author] ||= :user
options.reverse_merge({:description => :notes})
end
end
end
end
end