3

I have a user model that has one profile. The profile has attribute such as name, gender, age, dob, designation. No fields in profile are mandatory. The user is created by an admin from only an email address and a password. All the profile fields are nil when user is created. Once the user is created he can sign up and edit his profile. When we go to user profile I want to show the user details and if a detail is not set, I want to show a 'not set'.

The go to approach would be to override the attributes in the profile model like:

def name
  super || 'not set'
end
def age
  super || 'not set'
end
//and so on 

but doing so creates a lot of code duplication. Doing <%= @user.name || 'not set'%> in the view also results in a lot of code duplication.

I have thought of including 'not set' as a default value for all the attributes in the migration but some of the fields are integer and date, so its not feasible and moreover we cannot add translation.

I looked into ActiveRecord Attributes and tried to set the default value to my string

class Profile < ApplicationRecord
attribute :name, :string, default: "not set"

but this is same like assigning the default value in the rails migration and doesn't work for other data types.

I am hoping for a method that can do something like

def set_default(attribute)
  attribute || 'not set'
end

Such scenarios must be quite common but I was quite surprised to not find any questions relating to this, here on stackoverflow or other places. I googled quite a lot but couldn't find a solution. Any links are also greatly appreciated.

Ursus
  • 29,643
  • 3
  • 33
  • 50
sonm10
  • 53
  • 2
  • 6

2 Answers2

2

Maybe some metaprogramming?

class YourModel
  %w(name age).each do |a| # Add needed fields
    define_method(a) do
      super() || 'not set'
    end
  end
end

This could be extracted to a concern and include it where you need it.

  • I tried like: %w(name age dob).each do |a| define_method(a) do super || 'not set' end end but i am getting an error: `implicit argument passing of super from method defined by define_method() is not supported. Specify all arguments explicitly` – sonm10 Jan 15 '18 at 10:40
  • Sorry, this requires to call `super()` with explicit parentheses, missed that. – Pedro Adame Vergara Jan 15 '18 at 10:44
1

I'd advise against setting a default in the model. Use a Presenter/Decorator to display default values for UI purposes.

This is a sample for Draper (https://github.com/drapergem/draper) but there are other decorator libraries, you could even write a basic decorator without adding a dependency:

class ProfileDecorator < Draper::Decorator
  DEFAULT = "not set".freeze
  def name
    model.name || DEFAULT
  end
end

# and then use it like:

profile.decorate.name 

As for the duplication: i'd prefer duplication over meta-programming most of the time. Easier to debug, read, find and understand, IMHO.

Pascal
  • 8,464
  • 1
  • 20
  • 31
  • This is a good advice but more a comment than an answer. Maybe you could add an example? – Stefan Jan 15 '18 at 14:58
  • Of course you are right, @Stefan :-). Added a sample. – Pascal Jan 15 '18 at 19:29
  • Thanks for the suggestion @pascal. Totally agree with meta-programming being more difficult to debug and understand(+ I'm quite new to rails). Correct me if i am wrong; decorator or presenters are preferable if we have both logic and view, and in this case i don't think theres much of a logic and using a gem(draper) just to address this would be an 'over reaction'. Moreover the meta-programming to address this is quite straight forward and simple so i think i will stick with Predos's answer. Thanks a lot though. – sonm10 Jan 16 '18 at 05:19
  • @sonm10 yes, i use decorators to extract view related logic (and having a default value is logic, IMHO). I see them as kind of helpers that are bound to a model. My models are usually purely for DB access and do not contain much else. If this is the only place you would decorators, then it might be overkill to add a Gem/Dependency, you are right. Still i'd skip the metaprogramming and add those methods in plain ruby. – Pascal Jan 16 '18 at 06:43