nahankid
nahankid

Reputation: 221

Dynamic delegation in Ruby

We have two classes - User and Group

class User < ActiveRecord::Base
  # Group Memberships
  has_many :memberships, dependent: :destroy
  has_many :groups, through: :memberships
end

class Group < ActiveRecord::Base
  # Group has a property called 'name'
  # Group members
  has_many :memberships, dependent: :destroy
  has_many :members, through: :memberships, source: :user
end

This is working just fine and we can create Groups and add Users to those groups.

What's the cleanest way to detect user's group membership? Something like: user.reviewer? where reviewer is the name of a group, without explicitly defining those methods. How can this be done using delegation?

We already have it working, albeit in an ugly way. Would appreciate the most elegant (and shortest) solution.

PS: We're on Rails 4.2.7

Upvotes: 2

Views: 856

Answers (2)

SteveTurczyn
SteveTurczyn

Reputation: 36860

this is a way you can get my_user.reviewer? to work... add the following to your User class:

def method_missing(name, *args, &block)
  if name.to_s.end_with?('?')
    group_name = name.to_s.chomp('?')
    if Group.find_by(name: group_name)
      return groups.pluck(:name).include?(group_name) ? true : false
    end
  end
  super
end  

Upvotes: 0

SteveTurczyn
SteveTurczyn

Reputation: 36860

Last time I looked delegate wasn't supported for has_many relationships.

The simplest way is to define a member_of? method. I'm doing it here with a private method and using a memoized list of group names.

class User < ActiveRecord::Base
  # Group Memberships
  has_many :memberships, dependent: :destroy
  has_many :groups, through: :memberships
  delegate :name to :groups

  def member_of?(group_name)
    group_names.include? group_name.to_s
  end

  private

  def group_names
    @group_names ||= groups.pluck(:name)
  end

end

Use it like...

my_user.member_of?(:reviewers)

If you keep group names in the singular, you can still do the more readable pluralize call...

 my_user.member_of?(:reviewers)

And just reference the argument as group_name.to_s.singularize in the method.

Note that you can support my_user.reviewer? by adding a def method_missing call, but you'd have to check that the method is a valid group name and then check whether the user is a member of the group, otherwise raise the missing method error (i.e. super to the inherited method_missing). See here for a simple explanation of how method_missing can be used. https://rosettacode.org/wiki/Respond_to_an_unknown_method_call#Ruby

Upvotes: 1

Related Questions