Paco V.
Paco V.

Reputation: 97

Rails model call different class based on user flag

Learning Rails, I just faced something where some lights could be helpful.

I have the classes, A, B, C that all do an action.

And I have a Message model and I would like, when I am going to save, to call one of those classes, based on the user output.

I am struggling now on what would be the more rubyist way to write the code for the model but also the classes, depending on the model method.

Option A:

case @user.flag:
  when 'alpha'
    A.new(message)
  when 'beta'
    B.new(message)
  when 'gamma'
    C.new(message)

Option B: Moving A,B,C from classes to user flag Instance methods of a Module called Functions

Functions.send(@user.flag.to_sym,message)

Since I have little knowledge of Rails, I am looking for how to write the most clean and reusable code. Thanks in advance.

Upvotes: 0

Views: 582

Answers (2)

sameera207
sameera207

Reputation: 16629

This is an interesting question, as @user2490003 said, there is no write/wrong way of doing this.

Your approach will change depending on how your A,B and C classes implement and their representation and also what your method does in each class.

Let's take an example, a method called talk and two classes Man, Women.

So, you may implement this as

Individual class methods

class Man
  def talk
    # talk like an adult
  end
end

class Women
  def talk
    # talk like an adult
  end
end

However as you can see, this talk method is same for both Man and Women and also you can see they both normally share the same functionalities and attributes. So, create a base class called Human and move the talk method there

As a base class method

class Human
  def talk
    # talk like an adult
  end
end

class Man < Human

end

class Woman < Human

end

Now let's get an example of a baby and say baby talk differently than Man and Woman, although baby still inherits from Human. In such cases you can do

class Baby < Human
  def talk
    # baby talk
  end
end

What happens here is, Baby will inherit from Human , but when you call

Baby.new.talk # => baby talk

it execute the talk method in Baby class (not in the Human class)

Extracting the method to a module

Let's get a Parrot class, and assume that it has a talk method too, and also it's same as Human talk.

Now the problem we have is we cannot inherit Parrot class from Human, but we still want to have the code in the talk method. In a case like that, you can use a Module, so, you can do

module Talkable
  def talk
    # talk like an adult
  end
end

class Human
   include Talkable
end

class Parrot
  include Talkable
end

As, as I explained (or at least tried..), your implementation will depend on how your class A,B,C and Message classes are related.

What I personally do in situations like this is, get a pen and paper and try to map these objects without thinking about how to implement then in ruby or any language. Once you have an idea on how they all hang together, it's easy to find the syntax to implement it

Upvotes: 0

user2490003
user2490003

Reputation: 11890

As with many design decisions, there's numerous approaches you could, each of which would be "correct" mostly based on preference. Here's how I'd do it.

Firstly, I'd make sure @user.flags can only take on certain values since its value is being used to decide other actions. In Ruby the generally accepted way of handling these values is also as symbols since a given symbol is immutable.

Secondly, since you're doing something with the Message model after it's saved you can utilize the after_save callback and keep the action inside the Message model itself. This makes it more tied to the message model and makes it more readable in general.

Lastly, you'll want some sort of guarantee that your save/transaction rolls back if there's an error with your after_save action. Going off this answer you can do that by raising an error in `after_save_

In app/models/user.rb

class User < ActiveRecord::Base
  FLAGS = %w[alpha beta gamma].freeze

  # Ensuure that `flag` field can only take on certain pre-defined values
  # Also validate that flag can never be nil. You may need to change that
  # as needed for your application
  validates :flag, presence: true, inclusion: FLAGS

  def flag
    # This method isn't 100% necessary but I like to personally follow 
    # the pracitce of returning symbols for enumerated values
    super(flag).try(:to_sym)
  end
end

In app/models/message.rb

class Message < ActiveRecord::Base
  after_save :post_process_message

  private

  # I'd recommend a better name for this method based on what you're
  # specifically doing
  def post_process_message
    # Notice the more descriptive method name
    # Also no need to pass `message` as a param since it's now located
    # inside this model. You could also move it to a separate class/service
    # as needed but don't over-optimize until you need to
    send("handle_post_process_for_flag_#{user.flag}")
  rescue StandardError => e
    # Something went wrong, rollback!
    # It isn't "great practice" to rescue all errors so you may want to replace
    # this with whatever errrors you excpect your methods to throw. But if you
    # need to, it's fine to be conservative and rescue all on a case-by-case
    # basis
    raise ActiveRecord::RecordInvalid.new(self)
  end

  def handle_post_process_for_flag_alpha
  end

  def handle_post_process_for_flag_beta
  end

  def handle_post_process_for_flag_gamma
  end
end

Upvotes: 1

Related Questions