Git Product home page Git Product logo

Comments (28)

jasonperrone avatar jasonperrone commented on July 28, 2024 4

Well I hate to say it, but the answer is you're going to have change from a HABTM to a more explicit has_many, :through your join table. Then create the model for the join table. Each of the models joining through the join table will need audited, and has_associated_audits. The join table will need audited, and associated_with.

Some caveats to be aware of:

  1. I have not tried to have my join table model associated_with both of the models that use it, since I only needed to audit one of them at this time. I don't know if it will work to have two associated_withs in the join table model. Would be interested to see if anyone gets that to work.
  2. You very likely have no id field on your join table if you had been using HABTM. So, you will need to add one. Luckily the migration is simple. add_column :my_join_table, :id, :primary_key. Rails is smart enough to go back and fill in ids for all the existing records. Why do you have to do this? So that destroying a record or records from the join table will fire the before_destroy callback that audited uses. And further more, to make this work, you will need dependent: :destroy on all the has_many's of the two models related to the join table. This will cause destroy_all to be called when removing records from the join table as opposed to delete_all, which will not fire the callback.

from audited.

sachinswagh avatar sachinswagh commented on July 28, 2024 3

Referring to the main issue. I faced same problem with HABTM relationships.

  1. I switched the association from HABTM to has_many-through. I added the model for join table.
  2. Also added id column for join table using migration. Now in the model for join table add 'audited'.
    This tracks create events on join table.
  3. To track destroy event (if association gets removed) you need to make sure that you have added dependent: :destroy on has_many(join table relationship) and has_many-through associations both.
    Note: Adding dependent_destroy on has_many-through association does not make associated record to destroy.

from audited.

lucassch avatar lucassch commented on July 28, 2024 2

+1

from audited.

nynhex avatar nynhex commented on July 28, 2024 1

@jasonperrone Thanks again for the guidance on this. I wrote one gigantic case statement humanizing helper method that got me on track. Now my audit changes read like English (more or less). 👍

from audited.

jasonperrone avatar jasonperrone commented on July 28, 2024 1

That's a problem, the destroys, because while you have an audit of it, the auditable_id now points to nothing. What I did was I:

  1. added a new column to the audits table called resolved_associations
  2. Added an Observer on Audited::Audit, and before I create or save an audit record, I resolve all the associations and store them in that column. In other words, at this point in the life cycle, the audit record is not created yet, and the record of the thing you're auditing has not been destroyed yet, so you can access it. That means you can basically log into the audits table a JSON object of id => name, where name is some meaningful identifier for the record you're deleting. Like, for you it's Role. If the Role you're deleting is 'General Manager', then in the observer you can log 123 => 'General Manager' in the resolved_associations column you added to the audits table. Then in the future, when you have that audit record, you can obtain something meaningful for that role that was deleted. Instead of only having an auditable_id of 123, you can actually also obtain 'General Manager', which is arguably way more useful. And you're not limited to just that one column from the Role. You could put 123 => {'name' => 'General Manager', 'display_name' => 'A cool role to have','created_at' => '2018-01-01 00:00:00'}, or whatever.

I wrote that observer a long time ago so I haven't kept track of whether the Audited team has answered this problem themselves in a newer version, but I didn't see anything pop out at me in the Changelog.

from audited.

marcalc avatar marcalc commented on July 28, 2024

Anyone could please give a feedback on this? Thanks!

from audited.

 avatar commented on July 28, 2024

We are having a similar issue with has_many through - adding or deleting IDs using the magical <related_model>_ids attribute does not trigger audit entries, not sure why that bypasses acts_as_audited's hooks, still investigating - finding others having the same issues but no acts_as_audited idioms to resolve the issue yet.

from audited.

cj avatar cj commented on July 28, 2024

any update on this?

from audited.

 avatar commented on July 28, 2024

bump we are having the same issues

from audited.

 avatar commented on July 28, 2024

To clarify, we have a has_many through relationship

from audited.

 avatar commented on July 28, 2024

@netoisstools did you all ever resolve your issue?

from audited.

 avatar commented on July 28, 2024

@ayb Did you have any success in resolving this?

from audited.

ayb avatar ayb commented on July 28, 2024

No I have not

On Aug 28, 2012, at 10:33 AM, tesmar [email protected] wrote:

@ayb https://github.com/ayb Did you have any success in resolving this?


Reply to this email directly or view it on
GitHubhttps://github.com//issues/72#issuecomment-8093570.

from audited.

jasonperrone avatar jasonperrone commented on July 28, 2024

This never got resolved?

from audited.

quangv avatar quangv commented on July 28, 2024

thanks @jasonperrone !

from audited.

nynhex avatar nynhex commented on July 28, 2024

@jasonperrone I have a has_many_through relation ship between a Call model and SpecialEquipment. On the Call and SpecialEquipment model I have this set

  audited
  has_associated_audits

On the join table/model CallSpecialEquipment I have this set:

  audited :associated_with => :call
  audited :associated_with => :special_equipment

I really only care about the call model being audited the the special_equipment_ids attribute getting logged in an audit change. With this in place each time I manipulate a call and change the special_equipment_ids nothing gets logged. I'm on Rails 3.2.22 Ruby 1.9.3, and audited 3.0.

Am I doing something wrong or does this functionality not work in this version? Sorry to bump an old issue.

from audited.

jasonperrone avatar jasonperrone commented on July 28, 2024

How are you "manipulating" the special_equipment_ids? For example, given a call with a collection of CallSpecialEquipment, if you do call.call_special_equipments.clear in Rails console, it doesn't log the destroys?

from audited.

nynhex avatar nynhex commented on July 28, 2024

@jasonperrone Hey there, thanks for the speedy response. Actually it will log the destroy, I figured out that when I'm trying to display the audited changes I needed to do @call.associated_audits and run that through a block/loop to display the associated audits. I got it working.

One question I do have is how I can I get the audited_changes into a format that is more readable to the end user?

Here's an example of what gets logged and how it displays
{"to_hospital_time"=>[2016-05-25 22:14:11 UTC, 2016-05-31 20:04:37 UTC]}

It'd be nice if I could make this some how more explicit and inject some sort of verbiage to customize the message in a nicer format.

from audited.

jasonperrone avatar jasonperrone commented on July 28, 2024

Yeah, I personally wrote a helper method which takes the audited_changes column and parses it into human readable form. You'll need to something similar, in this case "Hi, you changed the 'To Hospital Time from #{Date.parse(audit.audited_changes['to_hospital_time'][0]).to_s} to #{Date.parse(audit.audited_changes['to_hospital_time'][1]).to_s} or something like that.

from audited.

nynhex avatar nynhex commented on July 28, 2024

@jasonperrone Yeah that's what I'm thinking about doing, writing a helper method for the view to make it more readable. Considering the objects change as per what's changed, do you have an example snippet of the helper method you wrote so I have a starting point? You can send me a gist or just paste here if you don't mind sharing your magic 👍

from audited.

jasonperrone avatar jasonperrone commented on July 28, 2024

There really isn't much more to it. I just iterate through each Audit record in the database Audited::Adapters::ActiveRecord::Audit.where('blah blah').each do |audit|
and for each audit record I case the type of change it is and the auditable type and then I manually construct a sentence very similar to what I did above.

audit_type = (audit.auditable_type + '_' + audit.action).to_sym
case audit_type
when :Role_create
"Created #{audit.audited_changes['display_name']}"
when :PermissionsRoles_create
"Added permission to role"

etc...

from audited.

laserlemon avatar laserlemon commented on July 28, 2024

It seems like this issue has been sufficiently worked around. I don't anticipate that full HABTM support will be attempted in Audited proper, as the focus is to track attributes inherent to the model being audited. Parent associations can be tracked easily because the foreign key is an attribute of the model being audited. Otherwise, moving into full association tracking is just outside the scope of the problem that Audited aims to solve. Thank you for posting the issue and I hope the advice here has helped!

from audited.

Kani999 avatar Kani999 commented on July 28, 2024

Hello,

How did you solve displaying information which was changed?

2.4.4 :015 > Audit.last.audited_changes
  Audit Load (3.2ms)  SELECT  `audits`.* FROM `audits` ORDER BY `audits`.`id` DESC LIMIT 1
 => {"user_id"=>1, "role_id"=>381} 

My log for joining table Users -> UserRoles <- Roles contains ids only so I don't know what was changed. The problem is when I destroy the role, I only know whichid` was deleted, but I cannot figure what type it was, because the related instance was deleted.

from audited.

Kani999 avatar Kani999 commented on July 28, 2024

@jasonperrone Thank you for the idea. I will try to figure out something similar to your advice.

from audited.

Kani999 avatar Kani999 commented on July 28, 2024

@jasonperrone

I've figured it by another way.

I've extended Audited in config/initializers/audited.rb

class AuditExtension < Audited::Audit
  before_save :resolve_association
  def resolve_association
    self.audited_changes = audited_changes.merge(auditable.audit_extension) if auditable.audit_extension.present?
  end
end

Audited.config do |config|
  config.audit_class = AuditExtension
end

This piece of code extend audited_changes variable, with audit_extension which can be load from the instance of audited object.

audit_extension attribute is defined in app/models/application_record.rb so this attribute can be accesible from any class.

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

  attr_accessor :audit_extension
end

Then in the UsersRole model, which joins Users and Roles together, I defined after_initialize method, which will initialize Hash which is merged into audited_changes column.

class UsersRole < ApplicationRecord
  # audited associated_with: :role
  audited associated_with: :user
  belongs_to :user
  belongs_to :role

  # set variable audit_extension (app/models/application.rb)
  # to setup other attributes in audited_changes
  after_initialize :set_attributes
  def set_attributes
    return unless role.present?

    audit_extension = {}
    audit_extension['name'] = role.name
    audit_extension['resource_type'] = role.resource_type
    audit_extension['resource_id'] = role.resource_id
    @audit_extension = audit_extension
  end

The changes now looks more user friendly (after/before):
image

from audited.

maurymmarques avatar maurymmarques commented on July 28, 2024

Hi,
Is there still no native solution to this problem?

from audited.

Kani999 avatar Kani999 commented on July 28, 2024

@maurymmarques
Hi, I'm still using the solution above (#72 (comment))
I don't know if there is any progress in this.

from audited.

natewallis avatar natewallis commented on July 28, 2024

@jasonperrone

I've figured it by another way.

I've extended Audited in config/initializers/audited.rb

class AuditExtension < Audited::Audit
  before_save :resolve_association
  def resolve_association
    self.audited_changes = audited_changes.merge(auditable.audit_extension) if auditable.audit_extension.present?
  end
end

Audited.config do |config|
  config.audit_class = AuditExtension
end

This piece of code extend audited_changes variable, with audit_extension which can be load from the instance of audited object.

audit_extension attribute is defined in app/models/application_record.rb so this attribute can be accesible from any class.

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

  attr_accessor :audit_extension
end

Then in the UsersRole model, which joins Users and Roles together, I defined after_initialize method, which will initialize Hash which is merged into audited_changes column.

class UsersRole < ApplicationRecord
  # audited associated_with: :role
  audited associated_with: :user
  belongs_to :user
  belongs_to :role

  # set variable audit_extension (app/models/application.rb)
  # to setup other attributes in audited_changes
  after_initialize :set_attributes
  def set_attributes
    return unless role.present?

    audit_extension = {}
    audit_extension['name'] = role.name
    audit_extension['resource_type'] = role.resource_type
    audit_extension['resource_id'] = role.resource_id
    @audit_extension = audit_extension
  end

The changes now looks more user friendly (after/before): image

I am doing something similar, I think there is one issue with this though in that it will kill the rollback functionality of audited.

I think your approach combined with a custom column (or using the comments column) might be a slightly better approach if you plan on rolling back

from audited.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.