0

This question appears to be the most voted/relevant question on Stack Overflow about setting default values on models. Despite the numerous ways explored, none of them cover this issue.

Note that this is an API - so the default text for a reminder must be provided by the api, hence why I am storing in the database. Some users will wish to use that default, some will update it.

Here is my code that works, but it feels wrong, because I have had to set one of the default values (for :body) in the 'parent' (User model).

class User < ActiveRecord::Base    
  # Relationships
  belongs_to :account
  has_one :reminder

  before_create :build_default_reminder

  private
  def build_default_reminder
    build_reminder(body: get_default_body)
  end

  def get_default_body
    <<~HEREDOC
      This is a quick reminder about the outstanding items still on your checklist.
      Thank you,
      #{self.name}
    HEREDOC
  end
end

class Reminder < ApplicationRecord
  # Relationships
  belongs_to :user

  # Default attributes
  attribute :is_follow_up, :boolean, default: true
  attribute :subject, :string, default: "your checklist reminder!"
  attribute :greeting, :string, default: "Hi"
end

What I would much rather do is this, so that all defaults are in the same model (the child):

class User < ActiveRecord::Base    
  # Relationships
  belongs_to :account
  has_one :reminder    
end

class Reminder < ApplicationRecord
  # Relationships
  belongs_to :user

  # Default attributes
  attribute :is_follow_up, :boolean, default: true
  attribute :subject, :string, default: "your checklist reminder!"
  attribute :greeting, :string, default: "Hi"
  attribute :body, :string, default:
    <<~HEREDOC
      This is a quick reminder about the outstanding items still on your checklist.
      Thank you,
      #{self.user.name}
    HEREDOC
end

but this of course throws the error:

undefined method `user' for #<Class:0x007fa59d4d0f90>

The problem comes about because the default :body string must contain the name of the user. Is there a way to achieve this - ie. have all the defaults in reminder model, but somehow reference the user that created it?

rmcsharry
  • 5,363
  • 6
  • 65
  • 108
  • 1
    You could use I18n for the default text and make your models fallback to these I18n translations if the value is blank. something like `custom_body` (text value in DB) and then define the getter `def body ; custom_body.presence || I18n.t('defaults.body', username: user.name) ; end` (`;` is a shorthand for a newline) – MrYoshiji Mar 15 '18 at 19:58
  • @MrYoshiji That's an interesting idea, I didn't think of that. Seems like a better solution. I will try it. – rmcsharry Mar 15 '18 at 20:06

2 Answers2

1

I think the fact that this is tricky may be an indication that you might want to redesign this a little bit. In particular, it looks like you're mixing model logic with view logic.

You can fix this by moving the logic that actually creates the string closer to wherever it is going to be rendered (an ERB file, an email template, etc). Then, when you pass in the Reminder instance that you're generating the string for, the code that calls @reminder.user.name will work, as @reminder's .user association is not blank.

Adam Berman
  • 784
  • 5
  • 16
  • Thanks for you quick answer, but unfortunately I forgot to mention this is an API, and the user can over ride this default message (but most are not expected to, hence the API needs to provide this default, so it must be stored in the database for each user). Added that now to the question. – rmcsharry Mar 15 '18 at 19:54
0

I think the fact that this is tricky may be an indication that you might want to redesign this a little bit. In particular, it looks like you're mixing model logic with view logic.

This was a pertinent observation by @Adam Berman. So it's been redesigned - with help from the comment by @MrYoshiji so thanks guys!

I think the following is a better and more maintainable solution, especially once the app does need to be able to provide actual translations of these default english texts - so I will also create custom_greeting and custom_subject for those 2 attributes.

class User < ActiveRecord::Base    
  # Relationships
  belongs_to :account
  has_one :reminder    
end

class Reminder < ApplicationRecord
  # Relationships
  belongs_to :user

  # Default attributes
  attribute :subject, :string, default: "your checklist reminder!"
  attribute :greeting, :string, default: "Hi"

  def body=(value)
    self.custom_body = value
  end

  def body
    custom_body.presence || I18n.t('reminder.default_body', name: self.user.name)
  end
end

en.yml file:

  reminder:
    default_body: "\nThis is a quick reminder about the outstanding items still on your checklist.\
      \nPlease click the button below to review the items we're still waiting on.\
      \nThank you,\
      \n%{name}"

(this question helped a lot for the multi-line text in yml)

(this question helped understand the getter/setter - see the answer about storing prices from @pascal betz)

rmcsharry
  • 5,363
  • 6
  • 65
  • 108