1

I have a class structure using single table inheritance as follows:

CashAccount > AssetAccount > FinancialAccount

Relavent Controller Code:

def create
  @financial_account = FinancialAccount.new(financial_account_params)
  respond_to do |format|
    if @financial_account.save
      format.html { redirect_to @financial_account, notice: 'Account was successfully created.' }
    else
      format.html { render :new }
    end
  end
end

def update
  respond_to do |format|
    if @financial_account.update(financial_account_params)
      format.html { redirect_to @financial_account, notice: 'Account was successfully updated.' }
    else
      format.html { render :edit }
    end
  end
end

  def set_financial_account
    @financial_account = FinancialAccount.find_by_id(params[:id])
    unless @financial_account 
      redirect_to root_path, :flash => { :alert => "That Account does not exist." }
      return
    end       
  end

def update_params
  params.require(@financial_account.model_name.param_key)
        .permit(:name, :type, :description)
end

def create_params
  params.require(:financial_account)
        .permit(:name, :type, :description)
end

Create works. Update does not. There are no errors. The saves return true but the value is never changed. Here are some puts to debug the state as the object is updated:

puts @financial_account.changed? //false
@financial_account.assign_attributes(financial_account_params)
puts @financial_account.changed? //true
@financial_account.description_will_change!
puts @financial_account.changed? //true
puts @financial_account.description //New Value 1
puts @financial_account.save //true
puts @financial_account.description //Old Value
puts @financial_account.update(:description => "New Value 2") //true
puts @financial_account.description //Old Value
@financial_account.description = "New Value 3"
puts @financial_account.description //New Value 3
puts @financial_account.save //true
puts @financial_account.description //Old Value

Description is a simple text attribute of the FinancialAccount Class:

  create_table "financial_accounts", id: :uuid, default: "uuid_generate_v4()", force: :cascade do |t|
    ...
    t.text     "description"
    ...
  end

partial to_yaml print out of @financial_account to view stored value form user:

delegate_hash:
  name: !ruby/object:ActiveRecord::Attribute::FromUser
    name: name
    value_before_type_cast: Test Account
    type: *1
    value: Test Account
  description: !ruby/object:ActiveRecord::Attribute::FromUser
    name: description
    value_before_type_cast: New Value 1
    type: *1
    value: New Value 1

The database is PostgreSQL

UPDATE - Output requested by DjezzzL

Note: This is a multi-tenant app so i need to set business id

2.2.4 :001 > Business.current_id = 1
 => 1 
2.2.4 :002 > tmp = FinancialAccount.first
  FinancialAccount Load (0.6ms)  SELECT  "financial_accounts".* FROM "financial_accounts" WHERE "financial_accounts"."business_id" = $1  ORDER BY "financial_accounts"."id" ASC LIMIT 1  [["business_id", 1]]
 => #<RetainedEarningsAccount id: "0a282a84-2561-4820-8f21-b8063c1c2604", type: "RetainedEarningsAccount", name: "Owner's Retained Earnings", created_at: "2017-05-02 05:20:22", updated_at: "2017-05-02 05:20:22", description: "Old Value", business_id: 1, update_balance_flag: false, reference_number: "11", balance_cents: 0> 
2.2.4 :003 > tmp.description = 'new value'
 => "new value" 
2.2.4 :004 > p tmp.save
   (0.2ms)  BEGIN
  FinancialAccount Exists (1.0ms)  SELECT  1 AS one FROM "financial_accounts" WHERE ("financial_accounts"."reference_number" = '11' AND "financial_accounts"."id" != '0a282a84-2561-4820-8f21-b8063c1c2604' AND "financial_accounts"."business_id" = 1) LIMIT 1
  RetainedEarningsAccount Load (0.3ms)  SELECT  "financial_accounts".* FROM "financial_accounts" WHERE "financial_accounts"."type" IN ('RetainedEarningsAccount') AND "financial_accounts"."id" = $1 LIMIT 1  [["id", "0a282a84-2561-4820-8f21-b8063c1c2604"]]
   (0.2ms)  COMMIT
true
 => true 
2.2.4 :005 > p tmp.description
"Old Value"
 => "Old Value" 
2.2.4 :006 > p tmp.reload.description
  RetainedEarningsAccount Load (2.1ms)  SELECT  "financial_accounts".* FROM "financial_accounts" WHERE "financial_accounts"."type" IN ('RetainedEarningsAccount') AND "financial_accounts"."id" = $1 LIMIT 1  [["id", "0a282a84-2561-4820-8f21-b8063c1c2604"]]
"Old Value"
 => "Old Value"

Note: my attributes are being marked as dirty so not a duplicate of ActiveRecord not saving after updating attribute

Community
  • 1
  • 1
scottysmalls
  • 1,231
  • 17
  • 23
  • So you use before callback in your models? Or have any logging or monitoring gem included into your models? – spickermann May 14 '17 at 08:47
  • @spickermann No save or update callbacks for any of those models. I don't use logging or monitoring gems. – scottysmalls May 14 '17 at 10:11
  • @scottysmalls First I don't see any setter for @financial_account in your update action. Do you have any before_action callbacks? I talk about `def update; @financial_account = FinancialAccount.find(params[:id]); ; end` Return to update records values. Could you please provide following executions with corresponding results and SQL-queries: `tmp = FinancialAccount.first; tmp.description = 'new value'; p tmp.save; p tmp.description; p tmp.reload.description;` ? – DjezzzL May 14 '17 at 13:39
  • @DjezzzL Since the debugging doesn't fail when referencing financial_account i figured it could be assumed but the path in question does travel through the set_financial_account method so you are right - i should include it: see above. thanks for being thorough. I added the output for your queries above as well. – scottysmalls May 15 '17 at 14:13
  • @scottysmalls This is so interesting sql-queries output from tmp.save. Special with select id != 'id'. It seems like you have some special validations. So could you please share your models definitions. – DjezzzL May 15 '17 at 14:36
  • @DjezzzL Ah - that was a unique validation for the reference_number. Removing it removed that sql query but yielded similar results. validates :reference_number, presence: true, :if => :id validates_uniqueness_of :reference_number, :allow_blank => true, scope: :business_id – scottysmalls May 15 '17 at 14:51
  • @scottysmalls just with that poor information I cannot reproduce your bug right now. So please provide more information about definition of your models (callbacks / validations / default_scope ). It seems like something went wrong and AR decided to not execute an update query (record not exists / changes not applied ) – DjezzzL May 15 '17 at 15:03

1 Answers1

0
def create
  @financial_account = FinancialAccount.new(create_params)
  respond_to do |format|
    if @financial_account.save
      format.html { redirect_to @financial_account, notice: 'Account was successfully created.' }
    else
      format.html { render :new }
    end
  end
end

def update
  respond_to do |format|
    if @financial_account.update(update_params)
      format.html { redirect_to @financial_account, notice: 'Account was successfully updated.' }
    else
      format.html { render :edit }
    end
  end
end

private

def create_params
  params.require(@financial_account.model_name.param_key)
        .permit(:name, :type, :description)
end

def update_params
  params.require(:financial_account)
        .permit(:name, :type, :description)
end

Use ActiveModel::Naming#param_key to get the param key for a polymorphic model. Also in this case its better to use two separate methods for whitelisting as it reduces the amount of code paths.

max
  • 96,212
  • 14
  • 104
  • 165
  • Same results as before. Thank you for param_key - i was unaware of it and will use it from now on. i agree two separate methods is much better here. I updated my post (i think the method names should be swapped though.) – scottysmalls May 15 '17 at 13:49