0

对于措辞混乱的问题和可能令人困惑的解释,我深表歉意,但坦率地说,我很困惑。

我有一个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
4

1 回答 1

0

您可以为模型中的关联指定外键:

class ContactLink < ActiveRecord::Base
  belongs_to :person, :foreign_key => "id"
end
于 2012-06-29T01:33:22.063 回答