简体   繁体   中英

Define class and instance methods dynamically in concern

I have a concern:

# app/models/concerns/rolable.rb
module Rolable
  extend ActiveSupport::Concern

  included do
    rolify
    Role.find_each do |role|
      scope "#{role.name.pluralize}", -> { joins(:roles).where(roles: {name: send("#{role.name}_role_name")}).distinct }
    end

  end

 class_methods do
   Role.find_each do |role|
     define_method "#{role.name}_role_name" do
       role.name
     end

     define_method "#{role.name}_role_id" do
       role.id
     end
   end
 end

 Role.find_each do |role|
   define_method("#{role.name}?") do
     has_role? self.class.send("#{role.name}_role_name")
   end
 end

end

As you can see it defines a bunch of scopes, class methods and instance methods. But I'm not happy about repetition of Role.find_each do |role| ... end Role.find_each do |role| ... end .

How can I eliminate this duplication? I tried this

Role.find_each do |role|
  included do
    ...
  end
  class_methods do
    ...
  end
end

But it doesn't work because of multiple included blocks. I can extract Role.find_each in method, but it's not much better.

How can improve this code and remove duplication?

I think you have a bad logic here. You shouldn't use Role.find_each because new roles will not be available after initialize you base class or you will have to load explicitly your concern every time when you need it.

In rolify you have useful methods:

Forum.with_role(:admin)
Forum.with_role(:admin, current_user)
@user.has_role?(:forum, Forum)
...

If you are pretty sure that your inventory of roles won't expand, then maybe you can dynamically define a bunch of anonymous concerns instead of creating one concern for all roles.

# models/concerns/rolables.rb
# no need to call `find_each' because the number or roles will never exceed 1000
Rolables = Role.all.map do |role|
  Module.new do
    extend ActiveSupport::Concern

    included do
      scope "#{role.name.pluralize}", -> { joins(:roles).where(roles: {name: send("#{role.name}_role_name")}).distinct }

      define_method("#{role.name}?") do
        has_role? self.class.send("#{role.name}_role_name")
      end
    end

    class_methods do
      define_method "#{role.name}_role_name" do
        role.name
      end

      define_method "#{role.name}_role_id" do
        role.id
      end
    end
  end
end

And include all those concerns into your model:

# models/user.rb
class User
  Rolables.each{|concern| include concern}
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM