Comments (28)
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:
- 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.
- 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.
Referring to the main issue. I faced same problem with HABTM relationships.
- I switched the association from HABTM to has_many-through. I added the model for join table.
- 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. - 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.
+1
from audited.
@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.
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:
- added a new column to the audits table called resolved_associations
- 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.
Anyone could please give a feedback on this? Thanks!
from audited.
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.
any update on this?
from audited.
bump we are having the same issues
from audited.
To clarify, we have a has_many through relationship
from audited.
@netoisstools did you all ever resolve your issue?
from audited.
@ayb Did you have any success in resolving this?
from audited.
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.
This never got resolved?
from audited.
thanks @jasonperrone !
from audited.
@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.
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.
@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.
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.
@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.
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.
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.
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 which
id` was deleted, but I cannot figure what type it was, because the related instance was deleted.
from audited.
@jasonperrone Thank you for the idea. I will try to figure out something similar to your advice.
from audited.
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):
from audited.
Hi,
Is there still no native solution to this problem?
from audited.
@maurymmarques
Hi, I'm still using the solution above (#72 (comment))
I don't know if there is any progress in this.
from audited.
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 endThis piece of code extend
audited_changes
variable, withaudit_extension
which can be load from the instance of audited object.
audit_extension
attribute is defined inapp/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 endThen in the UsersRole model, which joins Users and Roles together, I defined
after_initialize
method, which will initialize Hash which is merged intoaudited_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
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)
- Rails 7.1.beta.1 - DEPRECATION WARNING: Module.deprecate without a deprecator is deprecated HOT 1
- Exclude Audited Gem Log Entries from PostgreSQL Database Storage
- Encryption for audited_changes / disabling the FILTERED feature HOT 4
- Audits with line of code location HOT 1
- Different kinds of User. HOT 2
- Updating an enum field on audited record while updating nested resource with touch: true creates a double audit
- `#touch` on record results in `NoMethodError` HOT 1
- Conditionally turn off audits in tests
- Auditing models with a Composite Primary Key - possible? HOT 1
- Ignoring deleted_at/deleted_on attributes
- Support for batch creation of audits HOT 2
- Support for BigInt HOT 1
- How to ignore impersonated user ?
- Specify action
- Add YAML safe load documentation
- Gem is not saving auditable_id, auditable_type for User model
- When combining old audits, oldest value should be kept
- Preloading audits is ignored when accessing specific revision
- Testing the ActiveRecord version would allow to use audited without Rails. HOT 1
- Is this open? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from audited.