views:

81

answers:

4

I'm fairly new to rails, working on a Rails 3 app with a Profile model for users.

In the profile Model I'd like to have a "name" entry, and I'd like to be able to access logical variations of it using simple syntax like:

user.profile.name = "John Doe"
user.profile.name.first = "John"
user.profile.name.last = "Doe"

Is this possible, or do I need to stick with "first_name" and "last_name" as my fields in this model?

+7  A: 

It's possible, but I wouldn't recommend it.

I would just stick with first_name and last_name if I were you and add a method fullname:

def fullname
  "#{first_name} #{last_name}"
end

Edit:

If you really do want user.profile.name, you could create a Name model like this:

class Name < ActiveRecord::Base
  belongs_to :profile

  def to_s
    "#{first} #{last}"
  end
end

This allows you to do:

user.profile.name.to_s  # John Doe
user.profile.name.first # John
user.profile.name.last  # Doe

I was looking for the Rails/Ruby equivalent of Django's def __unicode__(self):, but couldn't find it.

Anyone know how to do this in Ruby/Rails:

def __unicode__(self):
  return self.first + ' ' + self.last

user.profile.name # John Doe
captaintokyo
Ok, advice noted. For the sake of me learning Ruby would you mind showing how the "possible but not recommended" approach would work?
Andrew
Sure, just a sec...
captaintokyo
Agreed. We just spent almost six months spliting a single name into multiple fields because some idiot "designed" the database using a single field for that name. Worse off our customer service reps would also put company names into that field because there was not business name field! I wish I knew who designed that database so I could smack 'em on the nose with a newspaper and say, "No! Bad programmer! Bad!"
Mike Bethany
Cool, thanks for all the feedback and ideas all. I'll stick with first_name and last_name and use the fullname method to give me the options I need :)
Andrew
+1  A: 

FYI (assume you have a field fullname. ie your profile.name = "John Doe")

class Profile
  def name
    @splited_name ||= fullname.split # @splited_name would cache the result so that no need to split the fullname every time
  end
end

Now, you could do something like this:

user.profile.fullname   # "John Doe"
user.profile.name.first # "John"
user.profile.name.last  # "Doe"

Note the following case:

user.profile.fullname = "John Ronald Doe"
user.profile.name.first  # "John"
user.profile.name.second # "Ronald"
user.profile.name.last   # "Doe"

I agree with captaintokyo. You won't miss out the middle names. Also this method assume no Chinese, Japanese names are input. It's because those names contain no spaces in between first name and last name normally.

PeterWong
+4  A: 

As Capt. Tokyo said that's a horrible idea but here's how you would do it:

rails g model Users full_name:hash

Then you would store data in it like so:

user = User.new
user.full_name = {:first => "Forrest", :last => "Gump"}

Now your problems begin.

To search the field requires both names and you can't do a partial search like searching for all people with the same last name. Worst of all you can store anything in the field! So imagine another programmer mistypes one of the field names so for a week you have {:fist => "Name", :last => "Last"} being inserted into the database! Noooooooooooooooooo!

If you used proper field names you could do this:

user = User.new(:first_name => "First", :last_name => "Last")

Easy to read and no need for hashes. Now that you know how to do it the wrong way, do it the right way. :)

Mike Bethany
I'll go ahead and forget how to do this. I had no idea this was even possible.
Jason Noble
+1  A: 

The other answers are all correct, in so far as they ignore the #composed_of aggregator:

class Name
  attr_reader :first, :last

  def initialize(first_name, last_name)
    @first, @last = first_name, last_name
  end

  def full_name
    [@first, @last].reject(&:blank?).join(" ")
  end

  def to_s
    full_name
  end
end

class Profile < ActiveRecord::Base
  composed_of :name, :mapping => %w(first_name last_name)
end

# Rails console prompt
> profile = Profile.new(:name => Name.new("Francois", "Beausoleil"))
> profile.save!

> profile = Profile.find_by_first_name("Francois")
> profile.name.first
"Francois"

As noted on the #composed_of page, you must assign a new instance of the aggregator: you cannot just replace values within the aggregator. The aggregator class acts as a Value, just like a simple string or number.

I also sent a response yesterday with a very similar answer: How best to associate an Address to multiple models in rails?

François Beausoleil