Porting plugins to Redmine 4¶
- Porting plugins to Redmine 4
- No alias_method_chain
- No more attr_protected
- No more before_filter and so on
- ActionController::Parameters is no longer a Hash
- Migrations should extend ActiveRecord::Migration[<version>]
- New methods to access changes in after callbacks
- Mailer methods must have user as the first argument
- User.current rewritten in Mailer
- deliver_later should be used to deliver mail
No alias_method_chain¶
The proper way to fix this is to use prepend instead of include and super instead of foo_without_bar (in this way foo_with_bar becomes just foo).
Example:
module ModelPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :method, :changes
end
end
module InstanceMethods
def method_with_changes
method_without_changes
...
end
end
end
Model.send(:include, ModelPatch)
Should be:
module ModelPatch
def method
super
...
end
end
Model.send(:prepend, ModelPatch)
However, such fix is not always possible (e.g., for helpers). Moreover, it can even cause infinite loop (if another plugin uses alias_method_chain). If this is the case, the following not very good alternative can be used (actually, this is what alias_method_chain did):
alias_method :foo_without_bar, :foo alias_method :foo, :foo_with_bar
Generally, if your plugin can be used in different environments with other plugin, it’s safer to use the latter fix.
No more attr_protected¶
If you ported plugins to Redmine 3.x, you, probably, spent some time adding attr_protected to your models. Now, you need to remove it, cause this method no longer exists. Or, use the following code for backwards compatibility:
attr_protected :id if Rails::VERSION::MAJOR < 5
No more before_filter and so on¶
If you haven’t renamed before_filter into before_action in your controllers yet, it’s the time to do this, as before_filter no longer exists.
The same applies to prepend_before_filter, after_filter and so on.
ActionController::Parameters is no longer a Hash¶
So, you may need to convert it to Hash before using. The correct way to do this is:
params.permit(:foo, :bar).to_h
But, sometimes you may need to do this as follows:
params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params
Migrations should extend ActiveRecord::Migration[<version>]¶
When you run a database migration in Rails 5, it makes some assumptions (e.g., which indexes should be added), that it did not do before. Therefore, you are now forced to specify the version of Rails, for which the migration is written. All pre-Rails 5 migrations should use the version 4.2.
For backwards compatibility (i.e., to keep them working for Redmine 3), you may declare migrations as follow:
class YourMigration < Rails::VERSION::MAJOR < 5 ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
...
end
New methods to access changes in after callbacks¶
Before, in after callbacks (such as after_save) you could use common methods to check, which changes had been made to the object’s attributes. Now, such methods no longer return the changes – they behave as if the object has been reloaded.
To access the changes the new special methods were introduced:
- Instead of
<attribute>_changed?you should usesaved_change_to_<attribute>?. - Instead of
changesyou should useprevious_changes. - Instead of
changed?you should usesaved_changes?. - Instead of
<attribute>_wasyou should use<attribute>_before_last_save.
Thus, to preserve backwards compatibility you can write the code as follows:
Rails::VERSION::MAJOR < 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR < 1) ? attribute_changed? : saved_change_to_attribute?
Mailer methods must have user as the first argument¶
If a User object is not the first argument, the Mailer will raise an exception. This change was made for proper localization of email notifications (a special method checks the first argument of all methods before invoking them).
So, if you are monkey-patching a Mailer method, you’ll need to have different patched methods for Redmine 4 and previous versions, e.g., as follows:
if Redmine::VERSION::MAJOR > 3 base.send(:include, Redmine4InstanceMethods) else base.send(:include, Redmine3InstanceMethods) end
User.current rewritten in Mailer¶
If you have something like this in your Mailer method:
@author = User.current
You are in trouble, because User.current is now rewritten for each method, before invoking it (the first argument is used – see the previous section).
The best solution is to pass the user object as a additional argument to your Mailer method, e.g., as follows:
def object_updated(user, object, author) @author = author
deliver_later should be used to deliver mail¶
Now, Redmine can use a queue for delivering emails, what should speed up its work. To use this feature you need to call deliver_later instead of deliver. However...
To put an email into the queue Redmine needs to serialize its data, what sometimes crashes (not all data can be serialized). Therefore, I find it safer to still use deliver, for now (but, this should be fixed in some time).