Определение базы данных с вложенными атрибутами и HABTM - PullRequest
1 голос
/ 03 мая 2020

Я пытаюсь отслеживать спутники GPS, когда они пересекают небо. Я получаю сообщения «SKY» каждые 5 секунд, содержащие данные о местоположении и уровне сигнала для каждого видимого в данный момент спутника. База данных содержит таблицы для «небес», «sats», «треков» и «точек». Идея состоит в том, что каждое полученное небо будет добавлять точку местоположения к каждой дорожке его спутника. Схема ...

  create_table "points", force: :cascade do |t|
    t.bigint "track_id"
    t.integer "az"
    t.integer "el"
    t.integer "ss"
    t.boolean "used"
    t.boolean "duplicate"
    t.integer "PRN"
    t.bigint "sky_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["PRN"], name: "index_points_on_PRN"
    t.index ["created_at"], name: "index_points_on_created_at"
    t.index ["sky_id"], name: "index_points_on_sky_id"
    t.index ["track_id"], name: "index_points_on_track_id"
  end

  create_table "sats", force: :cascade do |t|
    t.integer "PRN"
    t.datetime "created_at", precision: 6
    t.datetime "updated_at", precision: 6
    t.index ["PRN"], name: "index_sats_on_PRN"
  end

  create_table "sats_skies", id: false, force: :cascade do |t|
    t.bigint "sky_id", null: false
    t.bigint "sat_id", null: false
    t.index ["sat_id", "sky_id"], name: "index_sats_skies_on_sat_id_and_sky_id"
    t.index ["sky_id", "sat_id"], name: "index_sats_skies_on_sky_id_and_sat_id"
  end

  create_table "skies", force: :cascade do |t|
    t.string "klass"
    t.string "tag"
    t.string "device"
    t.float "time"
    t.float "xdop"
    t.float "ydop"
    t.float "vdop"
    t.float "tdop"
    t.float "hdop"
    t.float "pdop"
    t.float "gdop"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["created_at"], name: "index_skies_on_created_at"
  end

  create_table "tracks", force: :cascade do |t|
    t.bigint "sat_id"
    t.index ["sat_id"], name: "index_tracks_on_sat_id"
  end

Определения модели: ...

class Sky < ApplicationRecord
  include ActionView::Helpers::NumberHelper

  has_and_belongs_to_many :sats, inverse_of: :sky, dependent: :destroy
  has_many :points, inverse_of: :sky

  accepts_nested_attributes_for :sats, allow_destroy: true
  accepts_nested_attributes_for :points, allow_destroy: true

end
class Sat < ApplicationRecord
  has_and_belongs_to_many :skies, inverse_of: :sat, autosave: true
  has_one :track, inverse_of: :sat

  accepts_nested_attributes_for :skies, allow_destroy: true
  accepts_nested_attributes_for :track, allow_destroy: true
end
class Track < ApplicationRecord
  has_many :points, inverse_of: :track
  belongs_to :sat, inverse_of: :track

  accepts_nested_attributes_for :points, allow_destroy: true

end
class Point < ApplicationRecord
  belongs_to :sky, inverse_of: :point     #, optional: true
  belongs_to :track, inverse_of: :point   #, optional: true

end

При получении сообщения о небе проверяется предыдущий небесное сообщение, чтобы увидеть, какой из его спутников также существовал там. Для найденных, точка просто добавляется в таблицу треков этого спутника. Если спутник не был виден на последнем небе, создается новая запись спутника, и его местоположение «точка» добавляется к дорожке нового спутника.

Обратите внимание, что запись точки имеет внешние ключи для обеих дорожек. и небо записывает.

Первоначально у меня все это работало, но я создавал запись неба, затем создавал записи спутниковой записи / дорожки (если необходимо), а затем сохранял точку. Сбой питания при обработке сообщения неба заставил базу данных содержать спутниковую дорожку без точек. Я понимаю, что мог бы просто окружить всю эту обработку блоком транзакций, но подумал, что более чистым решением будет создание всех различных компонентов для небесного сообщения в памяти и по завершении сохранение всей партии с помощью одного new_sky.save.

Но, как ни старайся, мне кажется, я не могу получить правильную комбинацию reverse_of, nested_attributes и других атрибутов определения, чтобы успешно сохранить результаты. Со всем, что настроено, как указано здесь, в настоящее время я получаю следующее при обработке первого спутника в первом полученном сообщении неба ...

D, [2020-05-03 10:37:32.410281000 -0500 CDT#83744] DEBUG -- GpsComms: ************* This sat (#8) not found in previous SKY, create new *************
E, [2020-05-03 10:37:32.436055000 -0500 CDT#83744] ERROR -- GpsComms: Sat Processing Failed
E, [2020-05-03 10:37:32.436454000 -0500 CDT#83744] ERROR -- GpsComms: {:Rescue=>"#<ActiveRecord::InverseOfAssociationNotFoundError: Could not find the inverse association for track (:point in Track)>"}
E, [2020-05-03 10:37:32.436786000 -0500 CDT#83744] ERROR -- GpsComms: Could not find the inverse association for track (:point in Track) - (ActiveRecord::InverseOfAssociationNotFoundError)

С другими комбинациями опций (reverse_of :, необязательно :, et c) Я получаю сообщения об ошибках, указывающие, что различные объекты должны существовать.

Я думаю, что я близко. Я надеюсь, что кто-нибудь может найти правильную комбинацию определений, чтобы заставить это работать.

Спасибо за любую помощь,

ОБНОВЛЕНИЕ: Я полагал, что ошибка выше ("Не удалось найти при попытке сохранить новый объект неба была сгенерирована обратная связь для трека (: точка в треке) "). Он на самом деле генерируется, когда новая точка добавляется в коллекцию точек.

На данный момент я создал новое небо с

sky = Sky.new (параметры)

Спутник создан с

sat = sky.sats.build (параметры)

Трек с

track = sat.build_track ( empty )

Теперь я могу сделать либо

new_point = sat.track.points. build (параметры)

или

sat.track.points << Point.new (параметры) </p>

для генерации обратного ассо c ошибка.

Надеюсь, что это поможет!

ОБНОВЛЕНИЕ: Благодаря предложению Eyselandi c ниже - вот исполняемый файл. Ошибка в последней строке с ошибкой «обратная связь».

# Activate the gem you are reporting the issue against.
gem 'activerecord', '6.0.0'
require 'active_record'
require 'minitest/autorun'
require 'logger'

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(
  adapter: 'postgresql', 
  database: 'database_name',
  username: 'username',
  password: 'password'
)
ActiveRecord::Base.logger = Logger.new(STDOUT)

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end


ActiveRecord::Schema.define do
  create_table "points", force: :cascade do |t|
    t.bigint "track_id"
    t.integer "az"
    t.integer "el"
    t.integer "ss"
    t.boolean "used"
    t.boolean "duplicate"
    t.integer "PRN"
    t.bigint "sky_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["PRN"], name: "index_points_on_PRN"
    t.index ["created_at"], name: "index_points_on_created_at"
    t.index ["sky_id"], name: "index_points_on_sky_id"
    t.index ["track_id"], name: "index_points_on_track_id"
  end

  create_table "sats", force: :cascade do |t|
    t.integer "PRN"
    t.datetime "created_at", precision: 6
    t.datetime "updated_at", precision: 6
    t.index ["PRN"], name: "index_sats_on_PRN"
  end

  create_table "sats_skies", id: false, force: :cascade do |t|
    t.bigint "sky_id", null: false
    t.bigint "sat_id", null: false
    t.index ["sat_id", "sky_id"], name: "index_sats_skies_on_sat_id_and_sky_id"
    t.index ["sky_id", "sat_id"], name: "index_sats_skies_on_sky_id_and_sat_id"
  end

  create_table "skies", force: :cascade do |t|
    t.string "klass"
    t.string "tag"
    t.string "device"
    t.float "time"
    t.float "xdop"
    t.float "ydop"
    t.float "vdop"
    t.float "tdop"
    t.float "hdop"
    t.float "pdop"
    t.float "gdop"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["created_at"], name: "index_skies_on_created_at"
  end

  create_table "tracks", force: :cascade do |t|
    t.bigint "sat_id"
    t.index ["sat_id"], name: "index_tracks_on_sat_id"
  end
end

class Sky < ApplicationRecord
  has_and_belongs_to_many :sats, inverse_of: :sky, dependent: :destroy
  has_many :points, inverse_of: :sky

  accepts_nested_attributes_for :sats, allow_destroy: true
  accepts_nested_attributes_for :points, allow_destroy: true
end

class Sat < ApplicationRecord
  has_and_belongs_to_many :skies, inverse_of: :sat, autosave: true
  has_one :track, inverse_of: :sat

  accepts_nested_attributes_for :skies, allow_destroy: true
  accepts_nested_attributes_for :track, allow_destroy: true
end

class Track < ApplicationRecord
  has_many :points, inverse_of: :track
  belongs_to :sat, inverse_of: :track

  accepts_nested_attributes_for :points, allow_destroy: true

end

class Point < ApplicationRecord
  belongs_to :sky, inverse_of: :point     #, optional: true
  belongs_to :track, inverse_of: :point   #, optional: true
end

class BugTest < Minitest::Test
  def test_association_stuff
    sky_attr= {
      :klass=>"SKY",
      :device=>"/dev/ttyAMA0",
      :xdop=>0.62,
      :ydop=>0.89,
      :vdop=>0.94,
      :tdop=>1.15,
      :hdop=>1.23,
      :gdop=>2.28,
      :pdop=>1.55
    }
    sky = Sky.new(sky_attr)

    sat = sky.sats.build(:PRN=>7)

    track = sat.build_track

    point_attr= {
      :PRN=>7,
      :el=>61,
      :az=>340,
      :ss=>21,
      :used=>true,
#      :sky_id=>nil,
      :duplicate=>false
    }
    point=sat.track.points.build( point_attr)  

    sky.save

  end
end

Ошибка:

Error:
BugTest#test_association_stuff:
ActiveRecord::InverseOfAssociationNotFoundError: Could not find the inverse association for track (:point in Track)
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/reflection.rb:240:in `check_validity_of_inverse!'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/reflection.rb:474:in `check_validity!'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:43:in `initialize'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations.rb:237:in `new'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations.rb:237:in `association'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:286:in `inverse_association_for'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:107:in `set_inverse_instance'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:187:in `initialize_attributes'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:318:in `block in build_record'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/core.rb:328:in `initialize'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/inheritance.rb:70:in `new'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/inheritance.rb:70:in `new'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/reflection.rb:158:in `build_association'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:317:in `build_record'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/collection_association.rb:108:in `build'
    /Users/sjf/.rbenv/versions/2.6.2/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/collection_proxy.rb:316:in `build'
    active_record_gem.rb:136:in `test_association_stuff'

...