Adding Custom Attribute In Audited Record - ruby-on-rails

I want to add custom attributes in the Audit Log like first_name, last_name using audited gem. Any idea how I can update the values of my custom attributes ?
#<AuditLog id: 1,
auditable_id: 2,
auditable_type: "User",
associated_id: nil,
associated_type: nil,
user_id: 2,
company_id: nil,
user_type: "User",
username: nil,
first_name: nil, last_name: nil, action: "update",
audited_changes: {"first_name"=>["Name1", "Name2"]}, version: 8,
comment: nil,
remote_address: "::1", request_uuid: "ed24f0d3-7ca7-42b1-b1a4-202222b38f7c", created_at: "2022-01-28 17:59:08">
enter code here
My user model contains these two attributes
t.column :first_name, :string
t.column :last_name, :string

It should just work. The audit record doesn't store discrete fields, it stores a YAML or jsonb representation of all changed fields. If you've added first_name to your users table, the audit record will automatically note any changes to that attribute.

Related

Rails 6: Can I create an associated record to delegate to when a new record is created but not saved?

I have a CustomerProfile model that is associated with Mobile that handles mobile number verification, sms notifications, etc…
When creating the customer profile, I would like to set the user's mobile number as if that column existed on the profile table itself. To this end, I've used delegate. However, the associated mobile record is not created until after the new profile is saved.
class CustomerProfile < ApplicationRecord
belongs_to :user, optional: true
has_one :mobile, autosave: true, dependent: :destroy
delegate :number, to: :mobile, prefix: :mobile
delegate :number=, to: :mobile, prefix: :mobile
after_create -> { create_mobile }
...
end
So, I can't create a new customer profile and set a mobile number. I have to save the record before I set the number.
[1] pry(main)> profile = CustomerProfile.new
TRANSACTION (0.1ms) BEGIN
=> #<CustomerProfile:0x00007fb371201300 id: nil, first_name: nil, last_name: nil, user_id: nil, created_at: nil, updated_at: nil>
[2] pry(main)> profile.mobile_number = "123"
Module::DelegationError: CustomerProfile#mobile_number= delegated to mobile.number=, but mobile is nil: #<CustomerProfile id: nil, first_name: nil, last_name: nil, user_id: nil, created_at: nil, updated_at: nil>
From what I understand from the docs, there isn't a callback for this.
I don't think it is possible because the mobile record needs an id to point to and customer profile doesn't get one until it is saved. Is that right?
Is there a way to create an association when the model is first created without the save step?
You can create associated objects in Rails before save.
For your has_one association it would look something like this
customer_profile = CustomerProfile.new
customer_profile.build_mobile
Now you can use customer_profile object in your form to make input field for taking mobile number or just use customer_profile.save to save customer_profile and mobile objects that have has_one relationship.

rails active records returning wrong class type

i created a new model in rails with the following commands:
rails g model prod_domain name:string type:string user_logon_name:string description:string email:string address:string company:string department:string dn:string sa_description:string is_sa:string sa_remap_description:string ownership:string comment:text
rake db:migrate
i added some function in the model like this:
class ProdDomain < ActiveRecord::Base
def self.search_by_id(keyword)
users = Array.new
ProdDomain.where(id: keyword).find_each do |user|
users.push(user)
end
return users
end
def self.search(keyword)
users = []
ProdDomain.where(dn: keyword).find_each do |user|
users.push(user)
end
users
end
end
however, when i try to find records in this model, i kept getting 'USER' class instead of 'ProdDomain' class, what am i doing wrong:
(byebug) ProdDomain.find(1).class
ProdDomain Load (0.4ms) SELECT prod_domains.* FROM prod_domains WHERE prod_domains.id = 1 LIMIT 1
User(id: integer, email: string, encrypted_password: string, reset_password_token: string, reset_password_sent_at: datetime, remember_created_at: datetime, sign_in_count: integer, current_sign_in_at: datetime, last_sign_in_at: datetime, current_sign_in_ip: string, last_sign_in_ip: string, created_at: datetime, updated_at: datetime, username: string, role: integer, dn: string, department: string, name: string)
It looks like you have a type column defined. In Rails the type column is used by default for indicating inheritance.
If you're not intending on using inheritance then you can rename the column (something like kind is common) or you can overwrite the column name that Rails will use Base.inheritance_column.

Compare two records and get the names of the changed columns in rails active record

i need to compare two active records and get the name of the changed column
#<Evaluation id: 1, name: "Foo", status: "yes", comments: "can be better", created_at: "2017-05-09 12:00:00", updated_at: "2017-05-09 12:00:00">
#<Evaluation id: 2, name: "Foo", status: "yes", comments: "keep up the good work", created_at: "2017-05-09 12:05:00", updated_at: "2017-05-09 12:05:00">
I need to compare these two records and get the names of the changed columns. In this case :comments, :created_at, :updated_at
I have tried using methods like eql?, record1.attributes.except('id') == record2.attributes.except('id')
These only return either true or false I need to get the column names as well.
This can be done by comparing each column but i am looking for any other option.
You can use a small method to do this for you :
def diff_active_record(record_a, record_b)
(record_a.attributes.to_a - record_b.attributes.to_a).map(&:first)
end
This method will take the two active_record objects(in this case : record_a , record_b ) to compare and generate an array of attribute names that have changed.
You may use it like this:
diff_active_record(Evaluation.find(1), Evaluation.find(2))
The above would result in [:id, :comments, :created_at, :updated_at]

Deleting records from HABTM association

I'm trying to do something fairly simple. I have two models, User and Group. For simplicity's sake, let's say they look like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
and
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, for some reason, I have a user that has the same group twice. In the Rails Console:
user = User.find(1000)
=> #<User id: 1000, first_name: "John", last_name: "Doe", active: true, created_at:
"2013-01-02 16:52:36", updated_at: "2013-06-17 16:21:09">
groups = user.groups
=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">, #<Group id: 1, name: "student", is_active: true,
created_at: "2012-12-24 15:08:59", updated_at: "2012-12-24 15:08:59">]
user.groups = groups.uniq
=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">]
user.save
=> true
And there is some SQL output that I've silenced. I would think that everything should be all set, but it's not. The groups aren't updated, and that user still has both. I could go into the join table and manually remove the duplicates, but that seems cludgy and gross and unnecessary. What am I doing wrong here?
I'm running Rails 3.2.11 and Ruby 1.9.3p392
Additional note: I've tried this many different ways, including using user.update_attributes, and using group_ids instead of the groups themselves, to no avail.
The reason this doesn't work is because ActiveRecord isn't handling the invalid state of duplicates in the habtm association (or any CollectionAssociation for that matter). Any ids not included in the newly assigned array are deleted - but there aren't any in this case. The relevant code:
# From lib/active_record/associations/collection_association.rb
def replace_records(new_target, original_target)
delete(target - new_target)
unless concat(new_target - target)
#target = original_target
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
target
end
The 'targets' being passed around are Arrays of assigned records. Note the call to delete(target - new_target) is equivalent in your case to delete(user.groups - user.groups.uniq) which results in an empty Array passed (since comparison is based on the id attribute of each record).
Instead, you'll need to clear out the association and then reassign the single group again:
group = user.groups.first
user.groups.clear
user.groups << group
This might be a way to cleanup those duplicates (it handles any number of groups of duplicate associations):
user = User.find(1000)
user.groups << user.groups.group_by(&:id).values.find_all {|v| v.size > 1}.each {|duplicates| duplicates.uniq_by! {|obj| obj.id}}.flatten.each {|duplicate| user.groups.delete(duplicate)}

Ruby On Rails ActiveRecord model not showing "blacklist" field

So I'm trying to rough out a design in Ruby, and so I ran
ruby script/generate scaffold item name:string description:text model:string manufacturers_name:string category:string weight:decimal upc:string ebay_cat_id:string blacklist:bool in_discovery:bool archived:bool
The only problem is that none of the bool fields are on the model. If I use ruby script/consol and run
item = Item.new
I get
#<Item id: nil, name: nil, description: nil, model: nil, manufacturers_name: nil, category: nil, weight: nil, upc: nil, ebay_cat_id: nil, created_at: nil, updated_at: nil>
Is there a limit to how many fields it will show on the object? I know the fields were created in the database... double checked that.
Come to think of it the date timestamps aren't on the object either. Any hints for me? Do I have to manually write accessors for these or what?
Have you tried:
blacklist:boolean
It looks like you must declare the full name, the docs say:
Instantiates a new column for the table. The type parameter is normally one of the migrations native types, which is one of the following: :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.
Just like you cannot use int, you must declare integer
To answer the second part of your question, Yes! there is a limit to the number of columns you may have, 4096.
It's likley that once the interpreter hit "bool" it nixed the latter column names and types, that is why you're probably missing your timestamps and what not.

Resources