Class Person
In: app/models/person.rb
Parent: ActiveRecord::Base

People have many names (through acts_as_nameable) but belong to one surname. This allows many people to share a surname (unlike other personal names).

Because of the way names for people work there is a lot of complication in using name types (e.g. distinguishing between first names and surnames). These conventions represent common English usage, but may not generalise across communities, and may need to be adapted.

There are a lot of find/create by name style methods to make it easier to automatically build up a list of people for things like importing from Our Story. They are not necessarily used by PeopleController.

*schema.rb* snippet:

  create_table "people", :force => true do |t|
    t.column "sex",           :string,  :limit => 16
    t.column "language_id",   :integer
    t.column "date_of_birth", :date
    t.column "date_of_death", :date
    t.column "description",   :text
    t.column "clan_id",       :integer
    t.column "surname_id",    :integer
    t.column "restriction",   :string
  end

Methods

Public Class methods

Splits the surname off the name, calls Person.correct_surname and then recombines the names.

[Source]

     # File app/models/person.rb, line 148
148:   def Person.correct_name name_string
149:     
150:     surname_string = name_string.scan(/\w*$/).first
151:     if surname_string == name_string
152:       # there is only one name, so treat it as a first name
153:       surname_string = "" 
154:       first_name = name_string
155:     elsif surname_string.downcase == 'bara' && name_string.downcase.scan(/\s+bara\s?bara$/).first
156:       # Bara Bara is a two word surname so it needs special handling
157:       surname_string = "Barabara"
158:       first_name = name_string.sub(/\s+\wara\s?\wara$/, "")
159:     else
160:       # remove the last name from the whole name
161:       first_name = name_string.sub(/\s*\w*$/, "")
162:     end
163: 
164:     corrected_name = [first_name]
165:     corrected_name << Person.correct_surname( surname_string )
166:     return corrected_name.join(' ')
167:   end

If a surname is not considered to be in use for a clan, then it may be a typo or alternate spelling of a more accepted name. This method attempts to replace any mispellings with the more accepted form. E.g. Person.correct_surname("Nunggarrmaggbarr") would return "Nunggumadjbarr" if both names had been entered into the database, but only "Nunggumadjbarr" was in_use and marked as a primary_name.

[Source]

     # File app/models/person.rb, line 174
174:   def Person.correct_surname surname_string
175:     surname = Name.find(:first, :conditions => "LOWER(name) = '" + surname_string.downcase + "' AND name_type = 'surname'", :order => 'object_type') unless surname_string.empty?
176:     if surname && surname.object.is_a?(Clan) 
177:       unless (surname.in_use && surname.primary_name)
178:         surnames = surname.object.names.find(:all, :conditions => "in_use = true AND primary_name = true AND name_type = 'surname'", :order => 'object_type')
179:         if surnames.size == 1
180:           surname = surnames.first
181:         else
182:           # have a best guess at which surname is the closest to the specified surname_string
183:           surnames.each do |name|
184:             surname = name if name.name.first.downcase == surname_string.first.downcase
185:           end
186:         end
187:       end
188:     end
189: 
190: #           params[:person][:clan_id] = surname.object.id
191:     return (surname.name rescue surname_string)
192:   end

Returns a person object who has all of the names in the given string. For example, Person.find_by_name("Bob Firth") will return people with the first name "Bob" AND the last name "Firth" (as well as anyone with the first or last name "Bob Firth").

[Source]

     # File app/models/person.rb, line 49
 49:   def Person.find_by_name name_string
 50:     debug = false
 51:     all_names = name_string.downcase.split " "
 52:     first_name = all_names.join " "
 53:     people = []
 54:     
 55:     # This first query does two things:
 56:     # 1. Finds any people with a first name equal to the entire name string
 57:     # 2. Finds people by any odd names (i.e. names with abnormal or no namy type, partiuclarly those which have been imported)
 58:     # n.b. People with their last name equal to the entire name string will be found in the last cycle of the loop
 59:     Name.find(:all, :conditions => ["LOWER(names.name) = ? AND names.object_type = 'Person' AND names.name_type != 'surname' ", name_string.downcase]).each do |name|
 60:       people += [name.object]
 61:     end
 62:     
 63:     if debug
 64:       searches = '<p><pre>' + search_sql + '</pre></p>'
 65:       people.each do |person|
 66:         searches << "<p> person found before loop:" + person.primary_names + "</p>"
 67:       end
 68:     end
 69: 
 70:     first_name = ""
 71:     last_name = all_names.join " "    
 72: 
 73:     while all_names.size >=  1
 74: 
 75:       # Set up the find parameters
 76:       first_name_find_params = {
 77:           :select => "people.*",
 78:           :joins => "INNER JOIN names ON people.id = names.object_id",
 79:           :conditions => "names.object_type = 'Person' AND LOWER(names.name) = \'" + first_name + "\' AND names.name_type = 'first name'"
 80:       }
 81: 
 82:       last_name_find_params = {
 83:           :select => "people.*",
 84:           :joins => "INNER JOIN names ON people.surname_id = names.id",
 85:           :conditions => "LOWER(names.name) = \'" + Person.correct_surname( last_name ).downcase  + "\'"
 86:       }
 87: 
 88:       # Perform the queries and add any results which match to the list
 89:       if first_name.empty? # only searching last names
 90:         people += Person.find(:all, last_name_find_params)
 91:       elsif last_name.empty? # only searching first names
 92:         people += Person.find(:all, first_name_find_params)
 93:       else # searching both first names and last names
 94:         people += ( Person.find(:all, first_name_find_params) & Person.find(:all, last_name_find_params) ) # & performs an intersect of the two results
 95:       end
 96:     
 97:       # Set the name variables for the next loop
 98:       first_name += first_name.empty? ? all_names.first  : ' ' + all_names.first
 99:       all_names.delete all_names.first
100:       last_name = all_names.join ' '
101: 
102:       if debug
103:         searches << '<p><pre>' + search_sql + '</pre></p>'
104:         people.each do |person|
105:           searches << "<p> people found so far:" + person.primary_names + "</p>"
106:         end
107:       end
108:       
109:       
110:       
111:     # The old way of doing this didn't work with with_scope (although it could be salvaged if I could access the scoping and
112:     # manually incorporate it into the queries, which would be more efficient).
113:   
114: #      search_sql = "SELECT people.* FROM people, names "
115: #      unless first_name.empty?
116: #        search_sql << "WHERE names.object_type = 'Person'
117: #        AND LOWER(names.name) = \'" + first_name + "\' AND names.name_type = 'first name'  AND names.object_id = people.id "
118: #      end
119: #      if first_name != "" &&  last_name != ""
120: #        search_sql << "INTERSECT SELECT people.* FROM people, names "
121: #      end
122: #      unless last_name.empty?
123: #        # this should really do a join of all names that relate to the same entity to correct for mispelled surnames
124: #        search_sql << "WHERE people.surname_id = names.id AND LOWER(names.name) = \'" + Person.correct_surname( last_name ).downcase  + "\'"
125: #        
126: #        # I did have this added, but it seems redundant...
127: #        # AND (names.name_type = 'surname')
128: #      end
129: #
130: #      people += Person.find_by_sql(search_sql + ';')
131: 
132:     end
133:     
134:     
135:     # sometimes nil people seem to be added (when they are sorrowed)
136:     people.delete nil
137:     
138:     if debug
139:       return searches
140:     elsif people.size == 0
141:       return nil
142:     end
143:   people
144:   end

Finds all people with names that match a pattern. This could be much more easily implemented through a ferret search now that we have search indexing with names.

[Source]

     # File app/models/person.rb, line 305
305:   def Person.find_like_name name_string
306:     all_names = name_string.downcase.split " "
307:     first_name = all_names.join " "
308:     people = []
309:     
310:     # This first query does two things:
311:     # 1. Finds any people with a first name equal to the entire name string
312:     # 2. Finds people by any odd names (i.e. names with abnormal or no name type, partiuclarly those which have been imported)
313:     # n.b. People with their last name equal to the entire name string will be found in the last cycle of the loop
314:     Name.find(:all, :conditions => ["LOWER(names.name) LIKE ? AND names.object_type = 'Person' AND names.name_type != 'surname' ", name_string.downcase + '%']).each do |name|
315:       people += [name.object]
316:     end
317:     
318:     first_name = ""
319:     last_name = all_names.join " "    
320:     while all_names.size >=  1
321:     
322:       # Set up the find parameters
323:       first_name_find_params = {
324:           :select => "people.*",
325:           :joins => "INNER JOIN names ON people.id = names.object_id",
326:           :conditions => "names.object_type = 'Person' AND LOWER(names.name) LIKE \'" + first_name + "%\' AND names.name_type = 'first name'"
327:       }
328: 
329:       last_name_find_params = {
330:           :select => "people.*",
331:           :joins => "INNER JOIN names ON people.surname_id = names.id",
332:           :conditions => "LOWER(names.name) LIKE \'" + Person.correct_surname( last_name ).downcase  + "%\'"
333:       }
334: 
335:       # Perform the queries and add any results which match to the list
336:       if first_name.empty? # only searching last names
337:         people += Person.find(:all, last_name_find_params)
338:       elsif last_name.empty? # only searching first names
339:         people += Person.find(:all, first_name_find_params)
340:       else # searching both first names and last names
341:         people += ( Person.find(:all, first_name_find_params) & Person.find(:all, last_name_find_params) ) # & performs an intersect of the two results
342:       end
343:     
344:       # Set the name variables for the next loop
345:       first_name += first_name.empty? ? all_names.first  : ' ' + all_names.first
346:       all_names.delete all_names.first
347:       last_name = all_names.join ' '
348:     end
349:   people.delete nil
350:   people
351:   end

[Source]

     # File app/models/person.rb, line 195
195:   def Person.find_or_create_by_name name_string, parameters={}
196:     params = {:person => {
197:                             :description=>"[Imported]",
198:                             "date_of_birth(1i)"=>"",
199:                             "date_of_birth(2i)"=>"",
200:                             "date_of_birth(3i)"=>"",
201:                             "date_of_death(1i)"=>"",
202:                             "date_of_death(2i)"=>"",
203:                             "date_of_death(3i)"=>"",
204:                             :sorrow=>"0",
205:                             :sex=>"",
206:                             :restriction => ""
207:                           },
208:                 :name => {
209:                             :name=>name_string,
210:                             :notes=>"",
211:                             :in_use=>false,
212:                             :language_id=> 2,
213:                             :name_type=>"imported",
214:                             :primary_name=>true
215:                           }
216:                 }
217:     params[:person] = params[:person].merge(parameters[:person]) if parameters[:person]
218:     params[:name] = params[:name].merge(parameters[:name])  if parameters[:name]
219: 
220:     # Find
221:     person = Person.find_by_name( name_string )
222:     
223:     if person
224:       return person.first
225:     end
226: 
227:     # or Create
228:     
229:     # first sort out surname and have a best guess at clan (but only if the surname hasn't been specified in the params)
230:     # Surely this should make use of Person.correct_surname.
231:     if person.nil?  && params[:surname].nil? && params[:name][:name_type] == "imported"
232:       surname_string = params[:name][:name].scan(/\w*$/).first
233:       if surname_string == params[:name][:name]
234:         # there is only one name, so treat it as a first name
235:         surname_string = "" 
236:       elsif surname_string.downcase == 'bara' && params[:name][:name].downcase.scan(/\s+bara\s?bara$/).first
237:         # Bara Bara is a two word surname so it needs special handling
238:       else
239:         # remove the last name from the whole name
240:         params[:name][:name].sub!(/\s*\w*$/, "")
241:       end
242:       surname = Name.find_by_name(surname_string, :conditions => "name_type = 'surname'") unless surname_string.empty?
243:       if surname && surname.object.is_a?(Clan) 
244:         params[:person][:clan_id] = surname.object.id
245:         unless (surname.in_use && surname.primary_name)
246:           surnames = surname.object.names.find(:all, :conditions => "in_use = true AND primary_name = true AND name_type = 'surname'")
247:           if surnames.size == 1
248:             surname = surnames.first 
249:           else
250:             surnames.each do |name|
251:               surname = name if name.name.first.downcase == surname_string.first.downcase
252:             end
253:           end
254:         end
255:       end
256:       
257:       if surname.nil?
258:         params[:surname] = {:name => surname_string,
259:         :notes => "This name was automatically created",
260:         :in_use => true,
261:         :language_id => 1,
262:         :name_type => "surname",
263:         :primary_name => true
264:         }
265:       end
266:     end
267: 
268:     
269:     if params[:surname]
270:       surname = Name.new( params[:surname] )
271:     end
272:     if surname
273:       params[:name][:name_type] = "first name"
274:       params[:name][:in_use] = true
275:     end
276: 
277:     # Now check again since the name might have been changed to correct the spelling! (This should really happen in find_by_name)
278:     updated_person = Person.find_by_name( params[:name][:name] + " " +  surname.name ).first rescue nil
279: 
280:     if updated_person
281:       return updated_person
282:     end
283: 
284:     # If the person's first name end in "a" then there's a good chance that it's an Anindilyakwa name
285:     params[:name][:language_id] = params[:name][:name].last == "a" ? 1 : params[:name][:language_id]
286:     
287:     # If the person's name looks like an Anindilyakwa name for a lady then set the sex to female
288:     params[:person][:sex] = ( params[:name][:name] =~ /[Dd]\w+a/ ) ? "female" : params[:person][:sex]
289: 
290:     # then create the person
291:     if person.nil?
292:       person = Person.new( params[:person] )
293:       person.names << Name.new( params[:name] )
294:       person.surname = surname if surname
295:       person.save
296:     else
297:       person = person.first
298:     end
299:     
300:     return person
301:   end

Public Instance methods

Returns the age of a person on a particular day (default is today). Age is in seconds so that it can easiliy be passed to distance_of_time_in_words (or the helper method age_in_words).

For example, in a view this might be used:

  John is <%= age_in_words @john.age %> old.

[Source]

     # File app/models/person.rb, line 473
473:   def age on_date=Date.today
474:     on_date=Date.today if on_date.nil?
475:     if !self.date_of_birth
476:       return nil
477:     elsif !self.date_of_death || on_date <= self.date_of_death
478:       age_in_days = on_date - self.date_of_birth
479:     else
480:       age_in_days = self.date_of_death - self.date_of_birth
481:     end
482:     age_in_days * 86400
483:   end

Ensure paramaters are well formatted before saving (converts sex to lower case).

[Source]

     # File app/models/person.rb, line 354
354:   def before_save
355:     self.sex = self.sex.downcase unless sex.nil?
356:   end

Returns a string which contains one first name which is in use for this person.

[Source]

     # File app/models/person.rb, line 408
408:   def first_name suggested_name = nil
409:     if suggested_name
410:       name = self.names.find( :first, :conditions => "LOWER(name) LIKE \'" + suggested_name.downcase + "%\' AND name_type = 'first name'", :order => 'in_use DESC' )
411:     else
412:       name = self.names.find( :first, :conditions => "in_use = true AND primary_name = true AND name_type='first name'", :order => 'name' )
413:     end
414:     
415:     unless name
416:       return pronoun
417:     end
418:     return name.name
419:   end

Returns all first names.

[Source]

     # File app/models/person.rb, line 403
403:   def first_names
404:     self.names.find( :all, :conditions => "in_use = true AND primary_name = true AND name_type='first name'", :order => 'name' )
405:   end

Returns all last names.

[Source]

     # File app/models/person.rb, line 422
422:   def last_names
423:     self.names.find( :all, :conditions => "in_use = true AND primary_name = true AND name_type='surname'", :order => 'name' )
424:   end

Returns a string which is a combination of all the names

[Source]

     # File app/models/person.rb, line 427
427:   def name_list
428:     all_names = self.names.find(:all) 
429:     all_names << self.surname unless self.surname.nil?
430:     return (all_names.collect{ |n| n.name }).join(" ")
431:   end

Overrides primary_names from acts_as_nameable, returns a string of the form "First_Name Last_Name"

This code could cause 2n + 1 queries to be executed when listing people, unless names and surnams are included as part of the query, e.g.

  Person.find(:all, :include => [:names, :surname])

Unfortunately the :select option gets discarded when using :include, so the query in PeopleConroller#list will not be able to do that which could cause performance problems there.

[Source]

     # File app/models/person.rb, line 366
366:   def primary_names
367:     
368:     # the 2n+1 could be reduced to n+1 with the following technique:
369:     #   name_conditions = "in_use = true AND primary_name = true AND (name_type='first name' OR name_type='surname') AND "
370:     #   name_conditions += self.surname_id ? "((names.object_id = " + self.id.to_s + " AND names.object_type = 'Person') OR names.id = " + self.surname_id.to_s + ")" : "names.object_id = " + self.id.to_s + " AND names.object_type = 'Person'"
371:     #   all_names = Name.find( :all, :conditions => name_conditions, :order => 'name' )
372:     #   # separate first names and surnames
373:     
374:     # collect the list of first names
375:     first_names = self.names.collect { |name| name.name if name.name_type == 'first name' && name.in_use && name.primary_name}
376:     first_names.compact! # get ride of nil values
377:     
378:     # collect the list of surnames
379:     surnames = self.names.collect { |name| name.name if name.name_type == 'surname' && name.in_use && name.primary_name}
380:     surnames << self.surname.name unless self.surname.nil? or surnames.include? self.surname.name
381:     surnames.compact!
382:     
383:     # Combine first names and surnames together to make a primary_names string
384:     unless first_names.empty?
385:       primary_names = first_names.join(" ")
386:       unless surnames.empty?
387:         primary_names += ' ' + surnames.join(" ")
388:       end
389:     else
390:       unless surnames.empty?
391:         primary_names = '*' + surnames.join(" ") + ' ' + self.sex
392:       else
393:         # no first names or surnames!
394:         all_names = self.names.find(:all).collect { |name| name.name }
395:         primary_names = all_names.empty? ? '*Unknown person' : '*' +  all_names.join(" ")
396:       end
397:     end
398:     primary_names += " (sorrow)" if self.sorrow == 1
399:     return primary_names
400:   end

Returns a string which is either ‘he’ or ‘she’ depending on the person‘s sex. It is possible to specify the grammatical case if you need a pronoun for the accusative or genetive case instead (i.e. his/her or him/her). Uses person‘s first name instead of a pronoun if sex is not specified.

[Source]

     # File app/models/person.rb, line 438
438:   def pronoun pronoun_case = 'nominative'
439:     case self.sex
440:     when 'female'
441:       case pronoun_case
442:       when "nominative"
443:         'she'
444:       when "accusative"
445:         'her'
446:       when "genitive"
447:         'her'
448:       end
449:     when 'male'
450:       case pronoun_case
451:       when "nominative"
452:         'he'
453:       when "accusative"
454:         'him'
455:       when "genitive"
456:         'his'
457:       end
458:     else
459:       case pronoun_case
460:       when "genitive"
461:         self.first_name + "'s"
462:       else
463:         self.first_name
464:       end
465:     end
466:   end

[Validate]