Reputation: 97
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
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
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
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)
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
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