mostlydev
mostlydev

Reputation: 723

Rails decorator method not being called in production, works in development

I add the following override for a controller that I inherit from a gem (Spree):

module Spree
  module Admin
    UsersController.class_eval do
      def index
        if params[:role].present?
          included_users = Spree::User.joins(:role_users).
              where( spree_role_users: { role_id: params[:role] } ).map(&:id)
          flash[:notice] = "Filtered in #{included_users.count} users"
          @users = @users.where(id: included_users)
        end
      end
    end
  end
end

Basically, it filters by an additional parameter on the Admin::UsersController controller. The source for that controller in the gem doesn't actually define the index method, so mine just gets called instead.

Now, this works perfectly well in development. However, in production, this method never gets called.

Is there something about class_eval that I'm not getting here? Shouldn't things like this work basically the same in production as they do in development?

Thanks for any help.

Upvotes: 0

Views: 1233

Answers (1)

max
max

Reputation: 102240

Decorators are objects that wrap another object. For example they are often used to wrap models with presentational logic.

class UserDecorator < SimpleDelegator
  def full_name
    "#{first_name} #{last_name}"
  end
end

> @user = UserDecorator.new(User.new(first_name: 'John', last_name: 'Doe'))
> @user.full_name
=> "John Doe"

This is not a decorator method - you are just reopening the class and adding a method. This is known as monkey-patching.

Using class_eval in this case exactly the same in this as using the class keyword:

module Spree
  module Admin
    class UsersController
      def index
        if params[:role].present?
          included_users = Spree::User.joins(:role_users).
              where( spree_role_users: { role_id: params[:role] } ).map(&:id)
          flash[:notice] = "Filtered in #{included_users.count} users"
          @users = @users.where(id: included_users)
        end
      end
    end
  end
end

With monkey-patches the key is ensuring that your class redefinition is read. I'm guessing the difference between dev and production is due to class catching which prevents the class from being read from /app if it has already been defined by the Spree gem. config/application.rb uses bundler to require all the gems when the application starts up.

You can assure that a monkey-patch is loaded by placing it in config/initializers as all files in that directory are loaded on startup.

But a better alternative to monkeypatching may be to instead subclass the vendor controller and route to it:

class MyUsersController < ::Spree::Admin::UsersController
   def index
     if params[:role].present?
       included_users = Spree::User.joins(:role_users).
         where( spree_role_users: { role_id: params[:role] } ).map(&:id)
       flash[:notice] = "Filtered in #{included_users.count} users"
       @users = @users.where(id: included_users)
    end
  end
end

see also:

Upvotes: 1

Related Questions