Search code examples
ruby-on-railsrubyruby-on-rails-3redmineredmine-plugins

Validation problem with a Redmine hook


I am having a validation problem when writing a Redmine plugin.

I'm writing a hook for the issue model, and as a part of the hook method, i would like to invalidate the creation of the issue, by adding a custom error:

  def controller_issues_new_before_save( context = { } )
     context[:issue].errors.add(:due_date, "A custom error")
  end

For testing purposes, I have written a patch that overwrites Issue.validate_on_create, but it seems that every time when entering validate_on_create errors.count is set to zero.

I need to stop the creation of the issue object , but only when an attribute is set into another model object.

I thought about writing this in the validate_on_create method, but then I would need to pass it the other object.

The first solution that I thought about would be to insert an additional field in the Issue model, and modify it inside the hook.

Something like :

  def controller_issues_new_before_save( context = { } )
    context[:issue].can_validate = false
  end

  def validate_on_create
    unless can_validate 
      errors.add("error", "A custom error")
    end
  end   

where Issue.can_validate is an addition to the Issue model

However, this does not seem the best approach here. Is there an easier way?


Solution

  • If you are wanting to validate data you should patch the models directly and not use hooks. Hooks are meant to be used to insert HTML onto a page or change the control flow of a controller. Using hooks also means that your code will only work for that one path through the application, so if someone creates an issue somewhere else then you code will not run.

    To create a patch you just need to do two things:

    1. Create a module that has your code
    2. Make Redmine include that module in it's Issue class

    I've done this exact thing in a plugin that adds a validation on an Issue to require that due dates are set in the future. The patch for it is rather simple so I'll include it here:

    module RedmineRequireIssueDueDateInFuture
      module Patches
        module IssuePatch
          def self.included(base)
            base.class_eval do
              unloadable
    
              validate :due_date_in_future
    
              protected
              def due_date_in_future
                return true if due_date.nil?
    
                if due_date.to_time < Date.today.beginning_of_day
                  errors.add :due_date, :not_in_future
                end
    
              end
    
            end
          end
        end
      end
    end
    

    Inside of the class_eval is where you would put your own code, I'd recommend using a different name than validate_on_create. Otherwise you might have problems with other code if they want to use that method too.

    The second part (including the module into Redmine) is rather easy. Just require the Issue class and use include to add it to the class.

    # init.rb
    require 'dispatcher'
    Dispatcher.to_prepare :redmine_require_issue_due_date_in_future do
      require_dependency 'issue'
      Issue.send(:include, RedmineRequireIssueDueDateInFuture::Patches::IssuePatch)
    end
    

    You need to wrap this in the Dispatcher to keep things working in development mode. I've written about it on my blog.

    Feel free to copy my plugin from github to make your changes, it's pretty simple. https://github.com/edavis10/redmine_require_issue_due_date_in_future