Skip to content

Commit

Permalink
Revert "ensure atomic access to and creation of components in mqtt-task"
Browse files Browse the repository at this point in the history
This reverts commit 250807a.
  • Loading branch information
timcowlishaw committed Oct 10, 2024
1 parent 6126f51 commit a5944b3
Show file tree
Hide file tree
Showing 8 changed files with 20 additions and 57 deletions.
13 changes: 2 additions & 11 deletions app/models/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,8 @@ class Component < ActiveRecord::Base
belongs_to :sensor

validates_presence_of :device, :sensor

# IMPORTANT: Validation of sensor/device uniqueness is done at the database level,
# as this allows us to use the create_or_find_by! method to atomically upsert components
# in the mqtt-task, avoiding component duplication due to race conditions.
# For some reason, create_or_find_by! ONLY works when the database constraint is
# the ONLY uniqueness constraint on those two values, so adding a rails validation here
# causes an error. Leaving the validations here commented out by way of documentation.
# See https://stackoverflow.com/questions/74566974/create-or-find-by-not-working-as-it-should-in-rails-6
# validates :sensor_id, :uniqueness => { :scope => [:device_id] }
# validates :key, :uniqueness => { :scope => [:device_id] }

validates :sensor_id, :uniqueness => { :scope => [:device_id] }
validates :key, :uniqueness => { :scope => [:device_id] }

before_validation :set_key, on: :create

Expand Down
2 changes: 1 addition & 1 deletion app/models/concerns/data_parser/storer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def timestamp_parse(timestamp)
end

def sensor_reading(device, sensor)
component = device.create_or_find_component_for_sensor_reading(sensor)
component = device.find_or_create_component_for_sensor_reading(sensor)
return nil if component.nil?
value = component.normalized_value( (Float(sensor['value']) rescue sensor['value']) )
{
Expand Down
19 changes: 8 additions & 11 deletions app/models/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,25 +103,25 @@ def sensor_map
components.map { |c| [c.key, c.sensor.id]}.to_h
end

def create_or_find_component_by_sensor_id(sensor_id)
def find_or_create_component_by_sensor_id(sensor_id)
return nil if sensor_id.nil? || !Sensor.exists?(id: sensor_id)
components.create_or_find_by!(sensor_id: sensor_id)
components.find_or_create_by(sensor_id: sensor_id)
end

def create_or_find_component_by_sensor_key(sensor_key)
def find_or_create_component_by_sensor_key(sensor_key)
return nil if sensor_key.nil?
sensor = Sensor.find_by(default_key: sensor_key)
return nil if sensor.nil?
components.create_or_find_by!(sensor_id: sensor.id)
components.find_or_create_by(sensor_id: sensor.id)
end

def create_or_find_component_for_sensor_reading(reading)
def find_or_create_component_for_sensor_reading(reading)
key_or_id = reading["id"]
if key_or_id.is_a?(Integer) || key_or_id =~ /\d+/
# It's an integer and therefore a sensor id
create_or_find_component_by_sensor_id(key_or_id)
find_or_create_component_by_sensor_id(key_or_id)
else
create_or_find_component_by_sensor_key(key_or_id)
find_or_create_component_by_sensor_key(key_or_id)
end
end

Expand Down Expand Up @@ -245,10 +245,7 @@ def remove_mac_address_for_newly_registered_device!

def update_component_timestamps(timestamp, sensor_ids)
components.select {|c| sensor_ids.include?(c.sensor_id) }.each do |component|
component.transaction do
component.lock!
component.update_column(:last_reading_at, timestamp)
end
component.update_column(:last_reading_at, timestamp)
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/raw_storer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def store data, mac, version, ip, raise_errors=false

metric_id = device.find_sensor_id_by_key(metric)

component = device.create_or_find_component_by_sensor_id(metric_id)
component = device.find_or_create_component_by_sensor_id(metric_id)
next if component.nil?

value = component.normalized_value( (Float(value) rescue value) )
Expand Down
23 changes: 0 additions & 23 deletions db/migrate/20241009174732_unique_index_on_components.rb

This file was deleted.

6 changes: 2 additions & 4 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2024_10_09_174732) do
ActiveRecord::Schema.define(version: 2024_08_12_081108) do
# These are extensions that must be enabled in order to support this database
enable_extension "adminpack"
enable_extension "hstore"
Expand Down Expand Up @@ -65,9 +65,7 @@
t.string "key"
t.integer "bus", default: 1, null: false
t.datetime "last_reading_at"
t.index ["device_id", "key"], name: "unique_key_for_device", unique: true
t.index ["device_id", "sensor_id"], name: "index_components_on_device_id_and_sensor_id", unique: true
t.index ["device_id", "sensor_id"], name: "unique_sensor_for_device", unique: true
t.index ["device_id", "sensor_id"], name: "index_components_on_device_id_and_sensor_id"
end

create_table "devices", id: :serial, force: :cascade do |t|
Expand Down
2 changes: 1 addition & 1 deletion spec/models/component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

it "validates uniqueness of board to sensor" do
component = create(:component, device: create(:device), sensor: create(:sensor))
expect{ create(:component, device: component.device, sensor: component.sensor) }.to raise_error(ActiveRecord::RecordNotUnique)
expect{ create(:component, device: component.device, sensor: component.sensor) }.to raise_error(ActiveRecord::RecordInvalid)
end

describe "creating a unique sensor key" do
Expand Down
10 changes: 5 additions & 5 deletions spec/models/device_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -635,19 +635,19 @@
end
end

describe "#create_or_find_component_by_sensor_id" do
describe "#find_or_create_component_by_sensor_id" do
context "when the sensor exists and a component already exists for this device" do
it "returns the existing component" do
sensor = create(:sensor)
component = create(:component, sensor: sensor, device: device)
expect(device.create_or_find_component_by_sensor_id(sensor.id)).to eq(component)
expect(device.find_or_create_component_by_sensor_id(sensor.id)).to eq(component)
end
end

context "when the sensor exists and a component does not already exist for this device" do
it "returns a new valid component with the correct sensor and device" do
sensor = create(:sensor)
component = device.create_or_find_component_by_sensor_id(sensor.id)
component = device.find_or_create_component_by_sensor_id(sensor.id)
expect(component).not_to be_blank
expect(component).to be_a Component
expect(component.valid?).to be(true)
Expand All @@ -660,13 +660,13 @@
context "when no sensor exists with this id" do
it "returns nil" do
create(:sensor, id: 12345)
expect(device.create_or_find_component_by_sensor_id(54321)).to be_blank
expect(device.find_or_create_component_by_sensor_id(54321)).to be_blank
end
end

context "when the id is nil" do
it "returns nil" do
expect(device.create_or_find_component_by_sensor_id(nil)).to be_blank
expect(device.find_or_create_component_by_sensor_id(nil)).to be_blank
end
end

Expand Down

0 comments on commit a5944b3

Please sign in to comment.