← Back to Upcase

Concern, factory or service object?


(Lenart Rudel) #1

I’d like to hear your opinion before I start moving code around. I have an app where users can create surveys. Each survey is created with some defaults and also creates subaccount for itself on mandrill. At first all this code was living in app/models/survey.rb. Later I decided to extract it and moved it to a module app/models/survey/template.rb which extends from ActiveSupport::Concern.

I am now thinking that this could actually be a service object (maybe in app/services/survey_creator.rb which would take care of the same thing but would not live under Survey class. Am I right? Does anyone see a better solution? I’m totally open for any suggestions regarding refactoring this model.

Here’s some code to give you an idea over what’s going on:

class Survey
module Template

  extend ActiveSupport::Concern

  DEFAULTS = {
    title: "My Survey",
    intro: "Welcome to our survey",
    thank_you: "Thank you for participating",
    locale: :en
  }.stringify_keys

  included do
    after_create :create_mandrill_subaccount
    after_create :create_trash_brand
    after_create :create_default_participations_group
    after_create :create_default_group_mail
    after_create :create_categories
  end

  # Methods needed on class-level Survey.my_method()
  module ClassMethods

    def create_from_template(researcher: nil, plan: nil)
      survey = Survey.new DEFAULTS
      survey.author = researcher
      survey.plan = plan
      survey.save!
      survey
    end

  end

private

  def create_mandrill_subaccount
    MandrillSubaccount.new(self).create
  end

  def create_trash_brand
    brands.create name: "None of the above", trash: true
  end

  def create_default_participations_group
    # the survey is not saved at this point
    group = self.participations_groups.create! title: "Default"
    self.update_attribute :default_participations_group, group
  end

  def create_default_group_mail
    group_mails.create! invitation_subject: "You're invited to a survey",
      invitation_body: "Please visit [[survey_url]] and fill out our survey.",
      reminder_subject: "A reminder to participate in a survey",
      reminder_body: "A while ago we sent you an email. Please visit [[survey_title_with_link]]"
  end

  def create_categories
    categories.create! category_type: "TextCategory", title: 'Brand Attributes', instructions: '...'
    categories.create! category_type: "TextCategory", title: 'Brand Values', instructions: '...'
    categories.create! category_type: "PhotoCategory", title: 'Brand Personality', instructions: '...'
    categories.create! category_type: "PhotoCategory", title: 'Brand Image', instructions: '...'
    categories.create! category_type: "PhotoCategory", title: 'Characters', instructions: '...'
  end

end
end

Reusing Private method from different class
(Sean Griffin) #2

I’d definitely pull this out into a service object.


(Lenart Rudel) #3

Thanks @seangriffin I’ll transform it right now. I’m having troubles with naming (no wonder many says naming is the hardest thing in programming). Would you be using a name lik SurveyTemplate or SurveyCreator or would you suggest anything different?

Maybe SurveyTemplate.new sounds more like I’m creating a new template, right? So maybe SurveyCreator.new.create(plan: :free) reads better?


(Sean Griffin) #4

Its usually good to keep verbs out of class names. Create is a great method name, but not on a class called Creator.


(Sean Griffin) #5

Maybe try and find a name relating to what it does with the survey besides adding it to the database.


(Travis Chase) #6

With all the after_create callbacks it looks like you could even extract out some more Service Objects to perform different tasks within the workflow. You could then have the SurveyCreator coordinate the workflow.