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
changes
you should useprevious_changes
. - Instead of
changed?
you should usesaved_changes?
. - Instead of
<attribute>_was
you 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).