对于措辞混乱的问题和可能令人困惑的解释,我深表歉意,但坦率地说,我很困惑。
我有一个contact_link模型:belongs_to
,除其他模型外,还有一个人模型。人物模型:has_many :events through => :contact_link
。_
问题是人模型有它的:id
字段(primary_key)和一个:person_id
字段(那种作为“公共”键的功能)。
问题是,当我尝试构造一个contact_link时,rails 按照惯例,会在名为 的列中查找与person 表关联:id
的链接表。它最终使用不用于关联的 ,认为它是person表的字段。person_id
:person_id
:id
这有意义吗?如果是这样,我该怎么做才能绕过它。这是工作代码,所以我无法更改架构。这是我的代码:
class RecordOfContactImporter
def self.import_data(contact_record_file)
Rails.application.csv_impl.parse(contact_record_file, :headers => true, :header_converters => :symbol) do |row|
next if row.header_row?
if participant = Participant.where(:p_id => row[:participant_id]).first
person = get_person_record(row)
person.save!
event = get_event_record(row, participant)
event.save!
contact = get_contact_record(row, event, person)
contact.save!
contact_link = get_contact_link_record(row, event, person, contact)
p contact_link.valid?
if contact_link.valid?
contact_link.save!
else
# File.open(contact_link_import_error_log, 'a') {|f| f.write("[#{Time.now.to_s(:db)}] contact_link record invalid for - #{row} - #{contact_link.errors.map(&:to_s)}\n") }
end
else
# TODO: log row in missig participants file
end
end
end
def self.contact_link_import_error_log
dir = "#{Rails.root}/log/contact_link_import_error_logs"
FileUtils.makedirs(dir) unless File.exists?(dir)
log_path = "#{dir}/#{Date.today.strftime('%Y%m%d')}_import_errors.log"
File.open(log_path, 'w') {|f| f.write("[#{Time.now.to_s(:db)}] \n\n") } unless File.exists?(log_path)
log_path
end
def self.get_person_record(row)
person = Person.where(:person_id => row[:person_id]).first
person = Person.new(:person_id => row[:person_id]) if person.blank?
person.first_name = row[:person_first_name] unless row[:person_first_name].blank?
person.last_name = row[:person_last_name] unless row[:person_last_name].blank?
person
end
def self.get_event_record(row, participant)
start_date = Date.strptime(row[:event_start_date], '%m/%d/%y')
event = Event.where(:participant_id => participant.id,
:event_type_code => row[:event_type],
:event_start_date => start_date).first
event = Event.new(:participant_id => participant.id,
:event_type_code => row[:event_type],
:event_start_date => start_date) if event.blank?
event.participant = participant
event.event_type_other = row[:event_type_other] unless row[:event_type_other].blank?
event.disposition = row[:disposition] unless row[:disposition].blank?
event.event_disposition_category_code = row[:event_disposition_category] unless row[:event_disposition_category].blank?
event.event_start_time = row[:event_start_time] unless row[:event_start_time].blank?
event.event_breakoff_code = row[:event_breakoff] unless row[:event_breakoff].blank?
event.event_comment = row[:event_comment] unless row[:event_comment].blank?
event
end
def self.get_contact_record(row, event, person)
contact_date = Date.strptime(row[:contact_date], '%m/%d/%y')
pre_existing_contact = nil
ContactLink.where(:event_id => event.id, :person_id => person.id).all.each do |cl|
contact = Contact.where(:id => cl.contact_id).first
pre_existing_contact = contact if contact.contact_date_date == contact_date && contact.contact_start_time == row[:contact_start_time]
pre_existing_contact
end
contact = pre_existing_contact unless pre_existing_contact.nil?
contact = Contact.new() if contact.blank?
contact.psu_code = row[:psu_code] unless row[:psu_code].blank?
contact.contact_disposition = row[:contact_disposition] unless row[:contact_disposition].blank?
contact.contact_type_code = row[:contact_type] unless row[:contact_type].blank?
contact.contact_type_other = row[:contact_type_pther] unless row[:contact_type_pther].blank?
contact.contact_date = row[:contact_date] unless row[:contact_date].blank?
contact.contact_start_time = row[:contact_start_time] unless row[:contact_start_time].blank?
contact.contact_end_time = row[:contact_end_time] unless row[:contact_end_time].blank?
contact.language_code = row[:language] unless row[:language].blank?
contact.language_other = row[:language_other] unless row[:language_other].blank?
contact.interpret_code = row[:interpret] unless row[:interpret].blank?
contact.interpret_other = row[:interpret_other] unless row[:interpret_other].blank?
contact.location_code = row[:location] unless row[:location].blank?
contact.location_other = row[:location_other] unless row[:location_other].blank?
contact.contact_private_code = row[:contact_private] unless row[:contact_private].blank?
contact.who_contacted_code = row[:who_contacted] unless row[:who_contacted].blank?
contact.contact_comment = row[:contact_comment] unless row[:contact_comment].blank?
contact
end
def self.get_contact_link_record(row, event, person, contact)
contact_link = ContactLink.where(:person_id => person.id, :event_id => event.id, :contact_id => contact.id).first
contact_link = ContactLink.new(:person_id => person.id, :event_id => event.id, :contact_id => contact.id) #if contact_link.blank?
populate_contact_link_attributes(contact_link, row)
populate_contact_link_ncs_coded_attributes(contact_link, row)
contact_link
end
def self.populate_contact_link_attributes(contact_link, row)
[ :id,
:psu_code,
:contact_link_id,
:contact_id,
:event_id,
:instrument_id,
:staff_id,
:person_id,
:provider_id,
:transaction_type
].each do |attribute|
contact_link.send("#{attribute}=", row[attribute]) unless row[attribute].blank?
end
end
def self.populate_contact_link_ncs_coded_attributes(pbs_list, row)
[ :psu
].each do |attribute|
contact_link.send("#{attribute}_code=", row[attribute]) unless row[attribute].blank?
end
end
end
这是测试代码:
# -*- coding: utf-8 -*-
require 'spec_helper'
module NcsNavigator::Core
describe RecordOfContactImporter do
context "uploading csv data" do
describe ".import_data" do
before(:each) do
# create Participants and a particular participant for an exisiting record
[11111111, 22222222, 11112222, 22221111].each { |id| Participant.create :p_id => id }
participant = Participant.where(:p_id => '11111111').first
# create a Person
person = Person.create! :person_id => 11111111
# create an event associated with a the particular participant
event = Event.new :event_type_code => 15, :event_start_date => '2012-04-10'
event.participant = participant
event.save!
# create a contact with a colliding date and time
contact = Contact.create!(:contact_date_date => Date.parse('2012-04-10'), :contact_start_time => '15:00')
# create a contact link to bring the contact together with the person and event
ContactLink.create!(:person => person, :event => event, :contact => contact, :staff_id => 1)
end
it "finds a person if one exists or creates one if it doesn't" do
Person.count.should == 1
RecordOfContactImporter.import_data(File.open("#{Rails.root}/spec/fixtures/data/ROC_Code_lists_and_Dispositions.csv"))
Person.count.should == 4
end
it "finds an event if one exists or creates one if it doesn't" do
Event.count.should == 1
RecordOfContactImporter.import_data(File.open("#{Rails.root}/spec/fixtures/data/ROC_Code_lists_and_Dispositions.csv"))
Event.count.should == 4
end
it "finds a contact if one exists or creates one if it doesn't" do
Contact.count.should == 1
RecordOfContactImporter.import_data(File.open("#{Rails.root}/spec/fixtures/data/ROC_Code_lists_and_Dispositions.csv"))
Contact.count.should == 4
end
it "creates a ContactLink from the data" do
ContactLink.count.should == 1
RecordOfContactImporter.import_data(File.open("#{Rails.root}/spec/fixtures/data/ROC_Code_lists_and_Dispositions.csv"))
ContactLink.count.should == 4
end
it "associates a ContactLink with a Person, Event, and Contact" do
person = Person.first
contact = Contact.first
event = Event.first
ContactLink.first.person.should == person
end
end
end
context ".get_person_record" do
context "with an existing person record" do
let(:person) { Factory(:person, :person_id => "bob") }
it "finds the record by person_id" do
row = get_first_data_row_from_csv("existing_person")
person.person_id.should == row[:person_id]
RecordOfContactImporter.get_person_record(row).should == person
end
it "sets the first name if given in row" do
row = get_first_data_row_from_csv("existing_person")
person.first_name.should == "Fred"
person = RecordOfContactImporter.get_person_record(row)
person.first_name.should == "Bobby"
end
it "does not update first name if the data in the row first name is blank" do
row = get_first_data_row_from_csv("existing_person_with_blank_name")
person.first_name.should == "Fred"
person = RecordOfContactImporter.get_person_record(row)
person.first_name.should == "Fred"
end
end
context "without an existing person record" do
before(:each) do
@row = get_first_data_row_from_csv("existing_person_with_blank_name")
end
it "builds a new person record if no person exists" do
person = RecordOfContactImporter.get_person_record(@row)
person.class.should == Person
end
end
end
end
end
这是其余的代码:
人
# == Schema Information
# Schema version: 20120607203203
#
# Table name: people
#
# id :integer not null, primary key
# psu_code :integer not null
# person_id :string(36) not null
# prefix_code :integer not null
# first_name :string(30)
# last_name :string(30)
# middle_name :string(30)
# maiden_name :string(30)
# suffix_code :integer not null
# title :string(5)
# sex_code :integer not null
# age :integer
# age_range_code :integer not null
# person_dob :string(10)
# person_dob_date :date
# deceased_code :integer not null
# ethnic_group_code :integer not null
# language_code :integer not null
# language_other :string(255)
# marital_status_code :integer not null
# marital_status_other :string(255)
# preferred_contact_method_code :integer not null
# preferred_contact_method_other :string(255)
# planned_move_code :integer not null
# move_info_code :integer not null
# when_move_code :integer not null
# date_move_date :date
# date_move :string(7)
# p_tracing_code :integer not null
# p_info_source_code :integer not null
# p_info_source_other :string(255)
# p_info_date :date
# p_info_update :date
# person_comment :text
# transaction_type :string(36)
# created_at :datetime
# updated_at :datetime
# being_processed :boolean
# response_set_id :integer
#
# -*- coding: utf-8 -*-
# A Person is an individual who may provide information on a participant.
# All individuals contacted are Persons, including those who may also be Participants.
require 'ncs_navigator/configuration'
class Person < ActiveRecord::Base
include MdesRecord
acts_as_mdes_record :public_id_field => :person_id, :date_fields => [:date_move, :person_dob]
ncs_coded_attribute :psu, 'PSU_CL1'
ncs_coded_attribute :prefix, 'NAME_PREFIX_CL1'
ncs_coded_attribute :suffix, 'NAME_SUFFIX_CL1'
ncs_coded_attribute :sex, 'GENDER_CL1'
ncs_coded_attribute :age_range, 'AGE_RANGE_CL1'
ncs_coded_attribute :deceased, 'CONFIRM_TYPE_CL2'
ncs_coded_attribute :ethnic_group, 'ETHNICITY_CL1'
ncs_coded_attribute :language, 'LANGUAGE_CL2'
ncs_coded_attribute :marital_status, 'MARITAL_STATUS_CL1'
ncs_coded_attribute :preferred_contact_method, 'CONTACT_TYPE_CL1'
ncs_coded_attribute :planned_move, 'CONFIRM_TYPE_CL1'
ncs_coded_attribute :move_info, 'MOVING_PLAN_CL1'
ncs_coded_attribute :when_move, 'CONFIRM_TYPE_CL4'
ncs_coded_attribute :p_tracing, 'CONFIRM_TYPE_CL2'
ncs_coded_attribute :p_info_source, 'INFORMATION_SOURCE_CL4'
belongs_to :response_set
# surveyor
has_many :response_sets, :class_name => "ResponseSet", :foreign_key => "user_id"
has_many :contact_links, :order => "created_at DESC"
has_many :instruments, :through => :contact_links
has_many :events, :through => :contact_links
has_many :addresses
has_many :telephones
has_many :emails
has_many :household_person_links
has_many :household_units, :through => :household_person_links
has_many :participant_person_links
has_many :participants, :through => :participant_person_links
# validates_presence_of :first_name
# validates_presence_of :last_name
validates_length_of :title, :maximum => 5, :allow_blank => true
accepts_nested_attributes_for :addresses, :allow_destroy => true
accepts_nested_attributes_for :telephones, :allow_destroy => true
accepts_nested_attributes_for :emails, :allow_destroy => true
before_save do
self.age = self.computed_age if self.age.blank?
end
##
# How to format the date_move attribute
# cf. MdesRecord
# @return [String]
def date_move_formatter
'%Y-%m'
end
##
# Determine the age of the Person based on the date of birth
# @return [Integer]
def computed_age
return nil if dob.blank?
now = Time.now.utc.to_date
offset = ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
now.year - dob.year - offset
end
##
# Display text from the NcsCode list GENDER_CL1
# cf. sex belongs_to association
# @return [String]
def gender
sex.to_s
end
##
# Override to_s to return the full name of the Person
# aliased as :name and :full_name
# @return [String]
def to_s
"#{first_name} #{last_name}".strip
end
alias :name :to_s
alias :full_name :to_s
##
# Helper method to set first and last name from full name
# Sets first name if there is no space in the name
# @param [String]
def full_name=(full_name)
full_name = full_name.split
if full_name.size >= 2
self.last_name = full_name.last
self.first_name = full_name[0, (full_name.size - 1) ].join(" ")
else
self.first_name = full_name
end
end
##
# Override default setter to also set Date value for use in calculations
def person_dob=(dob)
self[:person_dob] = dob
begin
self.person_dob_date = Date.parse(dob)
rescue
# Date entered is unparseable
end
end
def self_link
participant_person_links.detect { |ppl| ppl.relationship_code == 1 }
end
private :self_link
##
# The participant record associated with this person if any whose
# relationship is self
def participant
self_link.try(:participant)
end
##
# Create or update the participant record associated with this person whose
# relationship is self
def participant=(participant)
ppl = self_link
if ppl
ppl.participant = participant
else
participant_person_links.build(:relationship_code => 1, :person => self, :participant => participant, :psu => self.psu)
end
end
##
# A Person is a Participant if there is an association on the participant table
# @return [Boolean]
def participant?
!participant.nil?
end
##
# The Participant ppg_status local_code (cf. NcsCode) if applicable
# @return [Integer]
def ppg_status
participant.ppg_status.local_code if participant && participant.ppg_status
end
##
# Based on the current state, pregnancy probability group, and
# the intensity group (hi/lo) determine the next event
# cf. Participant.upcoming_events
# @return [String]
def upcoming_events
events = []
if participant?
participant.upcoming_events.each { |e| events << e }
else
events << "Pregnancy Screener"
end
events
end
##
# Builds a new ResponseSet for the Person associated with the given Survey
# and pre-populates questions to which we have previous data.
#
# @param [Survey]
# @return [ResponseSet]
def start_instrument(survey)
# TODO: raise Exception if survey is nil
return if survey.nil?
build_instrument(survey).tap do |instr|
instr.build_response_set(:survey => survey, :user_id => self.id)
prepopulate_response_set(instr.response_set, survey)
end
end
def prepopulate_response_set(response_set, survey)
# TODO: determine way to know about initializing data for each survey
reference_identifiers = ["prepopulated_name", "prepopulated_date_of_birth", "prepopulated_ppg_status", "prepopulated_local_sc", "prepopulated_sc_phone_number", "prepopulated_baby_name", "prepopulated_childs_birth_date"]
response_type = "string_value"
reference_identifiers.each do |reference_identifier|
question = nil
survey.sections_with_questions.each do |section|
section.questions.each do |q|
question = q if q.reference_identifier == reference_identifier
break unless question.nil?
end
break unless question.nil?
end
if question
answer = question.answers.first
value = case reference_identifier
when "prepopulated_name"
response_type = "string_value"
name
when "prepopulated_date_of_birth"
response_type = "string_value"
dob.to_s
when "prepopulated_ppg_status"
response_type = "integer_value"
ppg_status
when "prepopulated_local_sc"
response_type = "string_value"
NcsNavigatorCore.study_center_name
when "prepopulated_sc_phone_number"
response_type = "string_value"
NcsNavigatorCore.study_center_phone_number
else
# TODO: handle other prepopulated fields
nil
end
response_set.responses.build(:question => question, :answer => answer, response_type.to_sym => value)
end
end
response_set
end
##
# Returns the number of times (0 based) this instrument has been taken for the given survey
# @param [Survey]
# @return [Fixnum]
def instrument_repeat_key(survey)
response_sets_for_survey = response_sets.select { |rs| rs.survey.title == survey.title }
response_sets_for_survey.blank? ? 0 : response_sets_for_survey.size - 1
end
##
# Return the currently active ContactLink, if a person is associated with a Contact through
# a ContactLink and that ContactLink has not been closed (cf. Event#closed? and Contact#closed?)
#
# @return [ContactLink]
def current_contact_link
open_contact_links = contact_links.select { |link| !link.complete? }
return nil if open_contact_links.blank?
return open_contact_links.first if open_contact_links.size == 1
# TODO: what to do if there is more than one open contact?
end
##
# Create a new Instrument for the Person associated with the given Survey.
#
# @param [Survey]
# @return [ResponseSet]
def build_instrument(survey)
Instrument.new(:psu_code => NcsNavigatorCore.psu,
:instrument_version => Instrument.determine_version(survey.title),
:instrument_type => InstrumentEventMap.instrument_type(survey.title),
:person => self,
:survey => survey)
end
##
# Determine if this Person has started this Survey
# @param [Survey]
# @return [true, false]
def started_survey(survey)
ResponseSet.where(:survey_id => survey.id).where(:user_id => self.id).count > 0
end
##
# Get the most recent instrument for this survey
# @param [Survey]
# @return [Instrument]
def instrument_for(survey)
ins = Instrument.where(:survey_id => survey.id).where(:person_id => self.id).order("created_at DESC")
ins.first
end
##
# Convenience method to get the last incomplete response set
# @return [ResponseSet]
def last_incomplete_response_set
rs = response_sets.last
rs.blank? ? nil : (rs.complete? ? nil : rs)
end
##
# Convenience method to get the last completed survey
# @return [ResponseSet]
def last_completed_survey
rs = response_sets.last
rs.complete? ? rs.survey : nil
end
##
# Given a data export identifier, return the responses this person made for that question
# @return [Array<Response>]
def responses_for(data_export_identifier)
Response.includes([:answer, :question, :response_set]).where(
"response_sets.user_id = ? AND questions.data_export_identifier = ?", self.id, data_export_identifier).all
end
##
# Returns all DwellingUnits associated with the person's household units
# @return[Array<DwellingUnit]
def dwelling_units
household_units.collect(&:dwelling_units).flatten
end
##
# Returns true if a dwelling_unit has a tsu_is and this person has an association to the
# tsu dwelling unit through their household
# @return [true,false]
def in_tsu?
dwelling_units.map(&:tsu_id).compact.size > 0
end
##
# Returns the primary cell phone number for this person, or nil if no such
# phone record exists.
def primary_cell_phone
cell_code = Telephone.cell_phone_type.to_i
primary_contacts(telephones, :phone_rank_code) do |ts|
ts.detect { |t| t.phone_type_code == cell_code }
end
end
##
# Returns the primary cell phone number for this person, or nil if no such
# phone record exists.
def primary_home_phone
home_code = Telephone.home_phone_type.to_i
primary_contacts(telephones, :phone_rank_code) do |ts|
ts.detect { |t| t.phone_type_code == home_code }
end
end
##
# Returns the primary address for this person, or nil if no such address
# record exists.
def primary_address
primary_contacts(addresses, :address_rank_code, &:first)
end
##
# Returns the primary email for this person, or nil if no such email record
# exists.
def primary_email
primary_contacts(emails, :email_rank_code, &:first)
end
##
# @private
def primary_contacts(collection, code_key)
yield collection.select { |c| c.send(code_key) == 1 }
end
private
def dob
return person_dob_date unless person_dob_date.blank?
return Date.parse(person_dob) if person_dob.to_i > 0 && !person_dob.blank? && (person_dob !~ /^9/ && person_dob !~ /-9/)
return nil
end
end
class PersonResponse
attr_accessor :response_class, :text, :short_text, :reference_identifier
attr_accessor :datetime_value, :integer_value, :float_value, :text_value, :string_value
end
联系链接
# -*- coding: utf-8 -*-
# == Schema Information
# Schema version: 20120607203203
#
# Table name: contact_links
#
# id :integer not null, primary key
# psu_code :integer not null
# contact_link_id :string(36) not null
# contact_id :integer not null
# event_id :integer
# instrument_id :integer
# staff_id :string(36) not null
# person_id :integer
# provider_id :integer
# transaction_type :string(255)
# created_at :datetime
# updated_at :datetime
#
# -*- coding: utf-8 -*-
# Each Contact Link record associates a unique combination
# of Staff Member, Person, Event, and/or Instrument that occurs during a Contact.
# There should be at least 1 contact link record for every contact.
class ContactLink < ActiveRecord::Base
include MdesRecord
acts_as_mdes_record :public_id_field => :contact_link_id
ncs_coded_attribute :psu, 'PSU_CL1'
belongs_to :contact
belongs_to :person
belongs_to :event
belongs_to :instrument, :inverse_of => :contact_link
belongs_to :provider
# belongs_to :staff # references public_id of staff in ncs_staff_portal
delegate :participant, :to => :event
# Validating :contact_id instead of :contact prevents a reload of
# the associated contact object when creating a contact link
# alone. This provides a huge speedup in the importer; if validating
# :contact is necessary, we should provide a scoped validation so it
# can be