Reputation: 52698
I'm working with an external framework (redmine) which has one Project
model that has_many EnabledModules
.
Projects can have EnabledModules "attached" or "removed" via the module names, like this:
class Project < ActiveRecord::Base
...
has_many :enabled_modules, :dependent => :delete_all
...
def enabled_module_names=(module_names)
enabled_modules.clear
module_names = [] unless module_names && module_names.is_a?(Array)
module_names.each do |name|
enabled_modules << EnabledModule.new(:name => name.to_s)
end
end
end
I'd like to detect when new modules are attached/removed via callbacks on EnabledModule
, and not modify the "original source code" if possible.
I can detect "attachments" like this:
class EnabledModule < ActiveRecord::Base
belongs_to :project
after_create :module_created
def module_created
logger.log("Module attached to project #{self.project_id}")
end
end
I thought that a before_destroy
would work for detecting removals, but it will not.
This happens because the enabled_modules.clear
call on Project.enabled_module_names=
, doesn't invoke 'destroy' on the modules. It just sets their project_id
to nil. So I figured I should use a after_update
or before_update
.
If I use after_update
, how can I get the 'previous' project_id
?
If I use before_update
, how can I differentiate between modules that are 'just updated' and modules whose project_id is going to be reset to nil?
Should I use a totally different approach here?
EDIT: I just found out that I could get the old values with '_was' (i.e. self.project_was
). However, collection.clear
doesn't seem to trigger update callbacks. Any other solutions?
EDIT 2: Changed title
Upvotes: 0
Views: 2567
Reputation: 52698
I ended up reimplementing the enabled_module_names=
method of projects, including a file in vendor/plugins/my_plugin/lib/my_plugin/patches/project_patch.rb and alias.
module MyPlugin
module Patches
module ProjectPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
base.class_eval do
unloadable # Send unloadable so it will not be unloaded in development
# This replaces the existing version of enabled_module_names with a new one
# It is needed because we need the "destroy" callbacks to be fired,
# and only on the erased modules (not all of them - the default
# implementation starts by wiping them out in v0.8'ish)
alias_method :enabled_module_names=, :sympa_enabled_module_names=
end
end
module ClassMethods
end
module InstanceMethods
# Redefine enabled_module_names so it invokes
# mod.destroy on disconnected modules
def sympa_enabled_module_names=(module_names)
module_names = [] unless module_names and module_names.is_a?(Array)
module_names = module_names.collect(&:to_s)
# remove disabled modules
enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
# detect the modules that are new, and create those only
module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name) }
end
end
end
end
end
I had to include some code on my vendor/plugins/my_plugin/init.rb file, too:
require 'redmine'
require 'dispatcher'
# you can add additional lines here for patching users, memberships, etc...
Dispatcher.to_prepare :redmine_sympa do
require_dependency 'project'
require_dependency 'enabled_module'
Project.send(:include, RedmineSympa::Patches::ProjectPatch)
EnabledModule.send(:include, RedmineSympa::Patches::EnabledModulePatch)
end
Redmine::Plugin.register :redmine_sympa do
# ... usual redmine plugin init stuff
end
After this, I was able to detect deletions on enabled modules (via before_delete
) on my patcher.
Upvotes: 1
Reputation: 3215
It looks like revision 2473 onwards of Redmine should solve your problem. See the diffs here: http://www.redmine.org/projects/redmine/repository/diff/trunk/app/models/project.rb?rev=2473&rev_to=2319
Basically the code has been modified such that removed modules are destroyed rather than deleted, the difference being that model callbacks are not fired for deletes.
There's another related fix in revision 3036 that seems important (see http://www.redmine.org/issues/4200) so you might want to pick up at least that version.
Upvotes: 1
Reputation: 6301
Regarding:
If I use after_update, how can I get the 'previous' project_id
Maybe try project_id_was, it's provided by ActiveRecord::Dirty
Upvotes: 0