Amit Joki
Amit Joki

Reputation: 59232

Querying rails model based on two fields of which one is sometimes null

In my model User, there already exists an id column which is auto-incremented. Now I plan to have a unique name for the user.

Now, the user won't be asked for it in the sign up process, so by default, I would like the column to return "user" + id when called like

user.profile_name #=> should return "user#{user.id}" if nil

I don't want to duplicate the data, so I would want to keep the field nil until the user enters one.

So, I thought of creating a custom function(or overriding the profile_name if possible)

# model User.rb
def get_identifier
   profile_name ? profile_name : "user#{id}"
end

And using where to find the user

id, user = params[:identifier], nil
if id[0..3] == "user"
   user = User.find_by_id(id[3..-1].to_num)
else
   user = User.find_by profile_name: id

But this seems not to be a rails way. I would have to take care of both the cases even when querying.

Is there any way to simplify this in Rails that I'm missing? Else can the current approach be bettered (apart from refactoring the code into methods)?

P.S The question is tagged appropriately with the versions I'm using. Ruby version - 2.1.5

Upvotes: 0

Views: 59

Answers (2)

spickermann
spickermann

Reputation: 106802

I would argue that you need to backfill profile_names for existing users. Otherwise you might run into all kinds of interesting problems.

For example: There are the existing users 1 and 2, both do not have a unique username yet. User 1 has the profile_name user1, analog for user 2 which has user2 as the profile_name user2. Now user 1 decides to set a profile_name and he sets its profile_name to user2, what is a unique profile_name at the moment - at least from a Rails validator's point of view.

To avoid this problems: Always set a unique profile_name and backfill profile_names for existing users.

Upvotes: 0

Jon Evans
Jon Evans

Reputation: 189

I'd like to preface with this answer with my opinion that your hesitation to "duplicate the data", where the data is really just the id, might be uncalled for. It's not user-editable or anything, so I'm not sure what the downsides are if you're not counting bytes in your database size.

Given the constraints of the question, I'd look at an implementation like this:

class User
  def self.find_by_profile_name(profile_name)
    where(profile_name: profile_name).first || find_by_default_profile_name(profile_name)
  end

  def self.find_by_default_profile_name(profile_name)
    where(id: DefaultProfileName.from_profile_name(profile_name).id)
  end
end


class DefaultProfileName
  attr_accessor :id

  def self.from_profile_name(profile_name)
    new(profile_name.sub('user', ''))  
  end

  def initialize(id)
    self.id = id
  end

  def to_s
    "user#{id}"
  end
end

I'm sure this could be improved on, but the main takeaway is that encapsulating your default profile name functionality in its own class enables making this bit of functionality contained and more manageable.

Upvotes: 1

Related Questions