From e62ad99c45a3c1fe69c721a17ce452b80c72705a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Diego=20Piske?= Date: Fri, 22 Nov 2024 10:00:49 -0300 Subject: [PATCH] rename columns, start developing model integration --- gemfiles/rails_7.1.gemfile.lock | 2 +- gemfiles/rails_7.2.gemfile.lock | 2 +- gemfiles/rails_8.0.gemfile.lock | 2 +- .../templates/create_irontrail_changes.rb.erb | 16 ++--- lib/iron_trail.rb | 1 + lib/iron_trail/association.rb | 2 +- lib/iron_trail/change_model_concern.rb | 33 ++++++++++ lib/iron_trail/config.rb | 4 +- lib/iron_trail/irontrail_log_row_function.sql | 10 +-- lib/iron_trail/migration.rb | 5 ++ lib/iron_trail/model/query_methods.rb | 0 lib/iron_trail/reflection.rb | 4 +- lib/iron_trail/version.rb | 2 +- spec/dummy_app/app/models/irontrail_change.rb | 1 + spec/dummy_app/app/models/person.rb | 2 + .../migrate/20241112090542_setup_test_db.rb | 1 + spec/models/irontrail_change_spec.rb | 65 +++++++++++++++++++ spec/services/people_manager_spec.rb | 4 +- 18 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 lib/iron_trail/change_model_concern.rb create mode 100644 lib/iron_trail/model/query_methods.rb create mode 100644 spec/models/irontrail_change_spec.rb diff --git a/gemfiles/rails_7.1.gemfile.lock b/gemfiles/rails_7.1.gemfile.lock index 5fc418f..3c92494 100644 --- a/gemfiles/rails_7.1.gemfile.lock +++ b/gemfiles/rails_7.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - iron_trail (0.1.2) + iron_trail (0.1.3) rails (>= 7.1) request_store (~> 1.5) diff --git a/gemfiles/rails_7.2.gemfile.lock b/gemfiles/rails_7.2.gemfile.lock index 5983294..ff959a0 100644 --- a/gemfiles/rails_7.2.gemfile.lock +++ b/gemfiles/rails_7.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - iron_trail (0.1.2) + iron_trail (0.1.3) rails (>= 7.1) request_store (~> 1.5) diff --git a/gemfiles/rails_8.0.gemfile.lock b/gemfiles/rails_8.0.gemfile.lock index c798d7d..1756b0a 100644 --- a/gemfiles/rails_8.0.gemfile.lock +++ b/gemfiles/rails_8.0.gemfile.lock @@ -10,7 +10,7 @@ GIT PATH remote: .. specs: - iron_trail (0.1.2) + iron_trail (0.1.3) rails (>= 7.1) request_store (~> 1.5) diff --git a/lib/generators/iron_trail/templates/create_irontrail_changes.rb.erb b/lib/generators/iron_trail/templates/create_irontrail_changes.rb.erb index e587225..065d7db 100644 --- a/lib/generators/iron_trail/templates/create_irontrail_changes.rb.erb +++ b/lib/generators/iron_trail/templates/create_irontrail_changes.rb.erb @@ -4,22 +4,22 @@ class CreateIrontrailChanges < ActiveRecord::Migration[<%= ActiveRecord::Migrati t.column :id, :bigserial, null: false t.column :actor_type, :text t.column :actor_id, :text - t.column :record_table, :text - t.column :record_id, :text + t.column :rec_table, :text + t.column :rec_id, :text t.column :operation, :text t.column :rec_old, :jsonb t.column :rec_new, :jsonb - t.column :rec_changes, :jsonb + t.column :rec_delta, :jsonb t.column :metadata, :jsonb t.column :created_at, :timestamp, null: false end - # CREATE INDEX irontrail_changes_rec_id ON irontrail_changes (record_id); - # CREATE INDEX irontrail_changes_actor_id ON irontrail_changes (actor_id); - # CREATE INDEX irontrail_changes_rec_type ON irontrail_changes USING BRIN (record_table); - # CREATE INDEX irontrail_changes_actor_typr ON irontrail_changes USING BRIN (actor_type); - # CREATE INDEX irontrail_changes_created_at ON irontrail_changes USING BRIN (change_created_at); + add_index :irontrail_changes, :rec_id + add_index :irontrail_changes, :rec_table + add_index :irontrail_changes, :actor_id + add_index :irontrail_changes, :actor_type + add_index :irontrail_changes, :created_at, using: :brin end end diff --git a/lib/iron_trail.rb b/lib/iron_trail.rb index 24e4d2c..3611fa9 100644 --- a/lib/iron_trail.rb +++ b/lib/iron_trail.rb @@ -17,6 +17,7 @@ require 'iron_trail/association' require 'iron_trail/reflection' require 'iron_trail/model' +require 'iron_trail/change_model_concern' module IronTrail # These tables are owned by IronTrail and will always be ignored, that is, diff --git a/lib/iron_trail/association.rb b/lib/iron_trail/association.rb index b6f8293..3b17c65 100644 --- a/lib/iron_trail/association.rb +++ b/lib/iron_trail/association.rb @@ -14,7 +14,7 @@ def association_scope pk_value = owner._read_attribute(foreign_key) pk_table = owner.class.arel_table - scope.where!('record_id' => pk_value, 'record_table' => pk_table.name) + scope.where!('rec_id' => pk_value, 'rec_table' => pk_table.name) scope end diff --git a/lib/iron_trail/change_model_concern.rb b/lib/iron_trail/change_model_concern.rb new file mode 100644 index 0000000..39efa0b --- /dev/null +++ b/lib/iron_trail/change_model_concern.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module IronTrail + module ChangeModelConcern + extend ::ActiveSupport::Concern + + module ClassMethods + def where_object_changes_to(args = {}) + scope = all + + args.each do |col_name, value| + scope.where!( + ::Arel::Nodes::SqlLiteral.new("rec_delta->#{connection.quote col_name}->>1").eq( + ::Arel::Nodes::BindParam.new(value) + ) + ) + end + + scope + # where_object_changes(0, args) + end + + def where_object_changes_from(args = {}) + where_object_changes(0, args) + end + + private + + def where_object_changes(ary_index, args) + end + end + end +end diff --git a/lib/iron_trail/config.rb b/lib/iron_trail/config.rb index 8a3ed8b..970842f 100644 --- a/lib/iron_trail/config.rb +++ b/lib/iron_trail/config.rb @@ -7,12 +7,14 @@ class Config attr_accessor \ :track_by_default, :enable, - :ignored_tables + :ignored_tables, + :track_migrations_starting_at_version def initialize @enable = true @track_by_default = true @ignored_tables = nil + @track_migrations_starting_at_version = nil end end end diff --git a/lib/iron_trail/irontrail_log_row_function.sql b/lib/iron_trail/irontrail_log_row_function.sql index c37b9c6..16dbafc 100644 --- a/lib/iron_trail/irontrail_log_row_function.sql +++ b/lib/iron_trail/irontrail_log_row_function.sql @@ -30,7 +30,7 @@ BEGIN IF (TG_OP = 'INSERT') THEN INSERT INTO "irontrail_changes" ("actor_id", "actor_type", - "record_table", "operation", "record_id", "rec_new", "metadata", "created_at") + "rec_table", "operation", "rec_id", "rec_new", "metadata", "created_at") VALUES (actor_id, actor_type, TG_TABLE_NAME, 'i', NEW.id, row_to_json(NEW), it_meta_obj, NOW()); @@ -49,15 +49,15 @@ BEGIN END IF; END LOOP; - INSERT INTO "irontrail_changes" ("actor_id", "actor_type", "record_table", "operation", - "record_id", "rec_old", "rec_new", "rec_changes", "metadata", "created_at") + INSERT INTO "irontrail_changes" ("actor_id", "actor_type", "rec_table", "operation", + "rec_id", "rec_old", "rec_new", "rec_delta", "metadata", "created_at") VALUES (actor_id, actor_type, TG_TABLE_NAME, 'u', NEW.id, row_to_json(OLD), row_to_json(NEW), u_changes, it_meta_obj, NOW()); END IF; ELSIF (TG_OP = 'DELETE') THEN - INSERT INTO "irontrail_changes" ("actor_id", "actor_type", "record_table", "operation", - "record_id", "rec_old", "metadata", "created_at") + INSERT INTO "irontrail_changes" ("actor_id", "actor_type", "rec_table", "operation", + "rec_id", "rec_old", "metadata", "created_at") VALUES (actor_id, actor_type, TG_TABLE_NAME, 'd', OLD.id, row_to_json(OLD), it_meta_obj, NOW()); END IF; diff --git a/lib/iron_trail/migration.rb b/lib/iron_trail/migration.rb index 28fc951..55d7fac 100644 --- a/lib/iron_trail/migration.rb +++ b/lib/iron_trail/migration.rb @@ -10,6 +10,11 @@ def method_missing(method, *args) return result unless IronTrail.enabled? && method == :create_table + start_at_version = IronTrail.config.track_migrations_starting_at_version + if !running_from_schema && start_at_version + return result if self.version < Integer(start_at_version) + end + table_name = args.first.to_s return result if IronTrail.ignore_table?(table_name) diff --git a/lib/iron_trail/model/query_methods.rb b/lib/iron_trail/model/query_methods.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/iron_trail/reflection.rb b/lib/iron_trail/reflection.rb index b83aadd..3f2ed1a 100644 --- a/lib/iron_trail/reflection.rb +++ b/lib/iron_trail/reflection.rb @@ -31,10 +31,10 @@ def join_scope(table, foreign_table, foreign_klass) ) scope.where!( - table['record_id'] + table['rec_id'] .eq(foreign_value) .and( - table['record_table'] + table['rec_table'] .eq(foreign_table.name) ) ) diff --git a/lib/iron_trail/version.rb b/lib/iron_trail/version.rb index b68d0c3..1c69435 100644 --- a/lib/iron_trail/version.rb +++ b/lib/iron_trail/version.rb @@ -1,5 +1,5 @@ # frozen_literal_string: true module IronTrail - VERSION = '0.1.2' + VERSION = '0.1.3' end diff --git a/spec/dummy_app/app/models/irontrail_change.rb b/spec/dummy_app/app/models/irontrail_change.rb index 6bd7a96..ad65e16 100644 --- a/spec/dummy_app/app/models/irontrail_change.rb +++ b/spec/dummy_app/app/models/irontrail_change.rb @@ -2,6 +2,7 @@ class IrontrailChange < ApplicationRecord include PgParty::Model + include IronTrail::ChangeModelConcern range_partition_by { "(created_at::date)" } end diff --git a/spec/dummy_app/app/models/person.rb b/spec/dummy_app/app/models/person.rb index 8e8133f..863b40b 100644 --- a/spec/dummy_app/app/models/person.rb +++ b/spec/dummy_app/app/models/person.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Person < ApplicationRecord + include IronTrail::Model + has_many :guitars belongs_to :converted_by_pill, optional: true, diff --git a/spec/dummy_app/db/migrate/20241112090542_setup_test_db.rb b/spec/dummy_app/db/migrate/20241112090542_setup_test_db.rb index bfe11c1..53fa045 100644 --- a/spec/dummy_app/db/migrate/20241112090542_setup_test_db.rb +++ b/spec/dummy_app/db/migrate/20241112090542_setup_test_db.rb @@ -7,6 +7,7 @@ def up create_table :people, id: :bigserial, force: true do |t| t.string :first_name, null: false t.string :last_name, null: false + t.string :favorite_planet t.bigint :converted_by_pill_id t.timestamp :first_acquired_guitar_at end diff --git a/spec/models/irontrail_change_spec.rb b/spec/models/irontrail_change_spec.rb new file mode 100644 index 0000000..b179b29 --- /dev/null +++ b/spec/models/irontrail_change_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.describe IrontrailChange do + let(:person) { Person.create!(first_name: 'Arthur', last_name: 'Klarkey') } + + before do + # let's have other people to ensure tests are picking up the right person + @other_people = 3.times do |index| + Person.create!(first_name: "Joey#{index}", last_name: "Doe#{index}").tap do |t| + t.update!(first_name: "Ashley#{index}") + t.update!(last_name: "X#{index}") + end.reload + end + end + + describe 'IronTrail::ChangeModelConcern' do + it 'tests the test' do + # just ensure other people have been created + expect(Person.count).to be >= 3 + expect(IrontrailChange.count).to be >= 9 + end + + describe 'where_object_changes_to' do + before do + person.update!(first_name: 'Michael') + person.update!(first_name: 'Johnny', last_name: 'Tod') + person.update!(first_name: 'Michael', favorite_planet: 'Saturn') + person.update!(last_name: 'Cash') + end + + it 'finds the expected records' do + scope = person.iron_trails.where_object_changes_to(last_name: 'Cash') + expect(scope.count).to eq(1) + + expect(scope.first.rec_old).to include('first_name' => 'Michael', 'last_name' => 'Tod') + expect(scope.first.rec_new).to include('first_name' => 'Michael', 'last_name' => 'Cash') + expect(scope.first.rec_delta).to eq({ 'last_name' => ['Tod', 'Cash'] }) + + scope = person.iron_trails.where_object_changes_to(first_name: 'Michael') + .order({ + described_class.arel_table[:created_at] => :asc, + described_class.arel_table[:id] => :asc + }) + + expect(scope.first.rec_old).to include('first_name' => 'Arthur', 'last_name' => 'Klarkey') + expect(scope.first.rec_new).to include('first_name' => 'Michael', 'last_name' => 'Klarkey') + expect(scope.first.rec_delta).to eq({ 'first_name' => ['Arthur', 'Michael'] }) + + expect(scope.last.rec_old).to include('first_name' => 'Johnny', 'last_name' => 'Tod', 'favorite_planet' => nil) + expect(scope.last.rec_new).to include('first_name' => 'Michael', 'last_name' => 'Tod', 'favorite_planet' => 'Saturn') + expect(scope.last.rec_delta).to eq({ + 'first_name' => ['Johnny', 'Michael'], + 'favorite_planet' => [nil, 'Saturn'] + }) + + planet_changed_record = scope.last.clone + + scope = person.iron_trails.where_object_changes_to(favorite_planet: 'Saturn') + expect(scope.count).to eq(1) + + expect(scope.first.id).to eq(planet_changed_record.id) + end + end + end +end diff --git a/spec/services/people_manager_spec.rb b/spec/services/people_manager_spec.rb index 9e76c02..3c37679 100644 --- a/spec/services/people_manager_spec.rb +++ b/spec/services/people_manager_spec.rb @@ -9,7 +9,7 @@ expect(person.persisted?).to be true - results = ActiveRecord::Base.connection.execute("select * from irontrail_changes WHERE record_table='people' AND record_id=#{person.id}::text").to_a + results = ActiveRecord::Base.connection.execute("select * from irontrail_changes WHERE rec_table='people' AND rec_id=#{person.id}::text").to_a expect(results.length).to be 1 record_new = JSON.parse(results.first['rec_new']) @@ -46,7 +46,7 @@ people.each do |person| res = ActiveRecord::Base.connection.execute(<<~SQL).to_a SELECT * FROM irontrail_changes WHERE - record_table='guitars' AND rec_new->>'person_id'='#{person.id}' + rec_table='guitars' AND rec_new->>'person_id'='#{person.id}' ORDER BY id ASC SQL