# frozen_string_literal: true require "ruby-plsql" describe "OracleEnhancedAdapter custom methods for create, update and destroy" do include LoggerSpecHelper include SchemaSpecHelper before(:all) do ActiveRecord::Base.establish_connection(CONNECTION_PARAMS) @conn = ActiveRecord::Base.connection plsql.activerecord_class = ActiveRecord::Base schema_define do create_table :test_employees, force: true do |t| t.string :first_name, limit: 20 t.string :last_name, limit: 25 t.date :hire_date t.decimal :salary, scale: 2, precision: 8 t.text :description t.decimal :version, scale: 0, precision: 15 t.date :create_time t.date :update_time t.timestamps null: true end end @conn.execute <<~SQL CREATE OR REPLACE PACKAGE test_employees_pkg IS PROCEDURE create_employee( p_first_name VARCHAR2, p_last_name VARCHAR2, p_hire_date DATE, p_salary NUMBER, p_description VARCHAR2, p_employee_id OUT NUMBER); PROCEDURE update_employee( p_employee_id NUMBER, p_first_name VARCHAR2, p_last_name VARCHAR2, p_hire_date DATE, p_salary NUMBER, p_description VARCHAR2); PROCEDURE delete_employee( p_employee_id NUMBER); END; SQL @conn.execute <<~SQL CREATE OR REPLACE PACKAGE BODY test_employees_pkg IS PROCEDURE create_employee( p_first_name VARCHAR2, p_last_name VARCHAR2, p_hire_date DATE, p_salary NUMBER, p_description VARCHAR2, p_employee_id OUT NUMBER) IS BEGIN SELECT test_employees_seq.NEXTVAL INTO p_employee_id FROM dual; INSERT INTO test_employees (id, first_name, last_name, hire_date, salary, description, version, create_time, update_time) VALUES (p_employee_id, p_first_name, p_last_name, p_hire_date, p_salary, p_description, 1, SYSDATE, SYSDATE); END create_employee; PROCEDURE update_employee( p_employee_id NUMBER, p_first_name VARCHAR2, p_last_name VARCHAR2, p_hire_date DATE, p_salary NUMBER, p_description VARCHAR2) IS v_version NUMBER; BEGIN SELECT version INTO v_version FROM test_employees WHERE id = p_employee_id FOR UPDATE; UPDATE test_employees SET first_name = p_first_name, last_name = p_last_name, hire_date = p_hire_date, salary = p_salary, description = p_description, version = v_version + 1, update_time = SYSDATE WHERE id = p_employee_id; END update_employee; PROCEDURE delete_employee( p_employee_id NUMBER) IS BEGIN DELETE FROM test_employees WHERE id = p_employee_id; END delete_employee; END; SQL end after(:all) do @conn = ActiveRecord::Base.connection @conn.drop_table :test_employees, if_exists: true @conn.execute "DROP PACKAGE test_employees_pkg" end before(:each) do class ::TestEmployee < ActiveRecord::Base include ActiveRecord::OracleEnhancedProcedures validates_presence_of :first_name, :last_name, :hire_date # should return ID of new record set_create_method do plsql.test_employees_pkg.create_employee( p_first_name: first_name, p_last_name: last_name, p_hire_date: hire_date, p_salary: salary, p_description: "#{first_name} #{last_name}", p_employee_id: nil )[:p_employee_id] end # return value is ignored set_update_method do plsql.test_employees_pkg.update_employee( p_employee_id: id, p_first_name: first_name, p_last_name: last_name, p_hire_date: hire_date, p_salary: salary, p_description: "#{first_name} #{last_name}" ) end # return value is ignored set_delete_method do plsql.test_employees_pkg.delete_employee( p_employee_id: id ) end private def raise_make_transaction_rollback raise "Make the transaction rollback" end end @today = Date.new(2008, 6, 28) @buffer = StringIO.new end after(:each) do Object.send(:remove_const, "TestEmployee") ActiveRecord::Base.clear_cache! end it "should create record" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload expect(@employee.first_name).to eq("First") expect(@employee.last_name).to eq("Last") expect(@employee.hire_date).to eq(@today) expect(@employee.description).to eq("First Last") expect(@employee.create_time).not_to be_nil expect(@employee.update_time).not_to be_nil end it "should rollback record when exception is raised in after_create callback" do TestEmployee.after_create :raise_make_transaction_rollback @employee = TestEmployee.new( first_name: "First", last_name: "Last", hire_date: @today ) employees_count = TestEmployee.count expect { @employee.save }.to raise_error("Make the transaction rollback") expect(@employee.new_record?).to be_truthy expect(TestEmployee.count).to eq(employees_count) end it "should update record" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today, description: "description" ) @employee.reload @employee.first_name = "Second" @employee.save! @employee.reload expect(@employee.description).to eq("Second Last") end it "should rollback record when exception is raised in after_update callback" do TestEmployee.after_update :raise_make_transaction_rollback @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today, description: "description" ) @employee.reload @employee.first_name = "Second" expect { @employee.save }.to raise_error("Make the transaction rollback") @employee.reload expect(@employee.first_name).to eq("First") end it "should not update record if nothing is changed and partial writes are enabled" do TestEmployee.partial_writes = true @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload @employee.save! @employee.reload expect(@employee.version).to eq(1) end it "should update record if nothing is changed and partial writes are disabled" do TestEmployee.partial_writes = false @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload @employee.save! @employee.reload expect(@employee.version).to eq(2) end it "should delete record" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload empl_id = @employee.id @employee.destroy expect(@employee).to be_frozen expect(TestEmployee.find_by_id(empl_id)).to be_nil end it "should delete record and set destroyed flag" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload @employee.destroy expect(@employee).to be_destroyed end it "should rollback record when exception is raised in after_destroy callback" do set_logger TestEmployee.after_destroy :raise_make_transaction_rollback @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload empl_id = @employee.id expect { @employee.destroy }.to raise_error("Make the transaction rollback") expect(@employee.id).to eq(empl_id) expect(TestEmployee.find_by_id(empl_id)).not_to be_nil clear_logger end it "should set timestamps when creating record" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) expect(@employee.created_at).not_to be_nil expect(@employee.updated_at).not_to be_nil end it "should set timestamps when updating record" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.reload expect(@employee.created_at).to be_nil expect(@employee.updated_at).to be_nil @employee.first_name = "Second" @employee.save! expect(@employee.created_at).to be_nil expect(@employee.updated_at).not_to be_nil end it "should log create record" do set_logger @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) expect(@logger.logged(:debug).last).to match(/^TestEmployee Create \(\d+\.\d+(ms)?\) custom create method$/) clear_logger end it "should log update record" do (TestEmployee.partial_writes = false) rescue nil @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) set_logger @employee.save! expect(@logger.logged(:debug).last).to match(/^TestEmployee Update \(\d+\.\d+(ms)?\) custom update method with id=#{@employee.id}$/) clear_logger end it "should log delete record" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) set_logger @employee.destroy expect(@logger.logged(:debug).last).to match(/^TestEmployee Destroy \(\d+\.\d+(ms)?\) custom delete method with id=#{@employee.id}$/) clear_logger end it "should validate new record before creation" do @employee = TestEmployee.new( last_name: "Last", hire_date: @today ) expect(@employee.save).to be_falsey expect(@employee.errors[:first_name]).not_to be_blank end it "should validate existing record before update" do @employee = TestEmployee.create( first_name: "First", last_name: "Last", hire_date: @today ) @employee.first_name = nil expect(@employee.save).to be_falsey expect(@employee.errors[:first_name]).not_to be_blank end end