Track count of the new and updated records seperatly - ruby-on-rails

Currently working on a project where any admin can import a xlsx product sheet into active record. I've developed it so that the xlsx parser hands each unique product row to a job which either updates an existing product or creates a new based on the attributes given.
I would like to keep track of the count of products either updated and created per sheet imported, assets added etc, to display in the admin panel.
The method i use now is simply creating events with an associated product id's that respond to a save record conditional, which i then count up and display after the import is done.
if product.save
product.events.new(payload: 'save')
end
The problem with this technique is that i can't differentiate between if the product is new or has been updated.
Are there better techniques that are more suitable to achieve counting products saved while differentiating between if its updated or new?
TDLR;
Importing products to active record (1 job per product) from an excel sheet. What are the better practise's/techniques for keeping count of new and updated records seperatly per import.

You have several choices here:
A simple option, as per my comment, is simply to check the created_at and updated_at columns after the record is saved. If they're equal, it's a new record, if not, it means the record already existed and was updated. You would have something along these lines:
if product.created_at == product.updated_at
new_product_count += 1
else
updated_product_count += 1
end
However, there might be better ways to do this. Just as an example: If I understand correctly you keep track of the number of saved products by creating a new 'save' event. You could instead have two types of events: created and updated. (This would have the added benefit of allowing you to count how many times a product has been updated since it was created)

I don't know if this can help you but in these cases I use persisted? method.
person = Person.new(id: 1, name: 'bob')
person.persisted? # => false

Related

Ruby on Rails, creating a many to many association after an activerecord-import

I am creating a few records programmatically based on a users input and creating an array of records to import.
When I check the database I can see the relationship has been created if they are new records.
If one of the records already exists in the database I can see an entry of the following in the association table but I can also see the new records have been created in their respective table so they exist but the records ID is not being updated in the association table.
user_id: 1
keyword_id: null
but if I run the code for a second time it will add the relationship correctly.
This is my code
records_to_add = []
words.each do |word|
keyword = Keyword.find_or_initialize_by(
word: word,
device: device,
)
records_to_add.push(keyword)
end
keywords_added = Keyword.import records_to_add, on_duplicate_key_ignore: true, validate: true
user.keywords << records_to_add
I think there is something wrong with this part of the code
user.keywords << records_to_add
It isn't creating the relationship correctly if one of the records already exists...
You are calling 'find_or_initialize_by' in your words loop, and then importing those records, which creates a new row in your Keyword table for all the new records.
So far, so good.
Then your script takes the first list (persisted and new records) and attempts to associate them to the user. At this point, it creates associations for existing Keyword records, but tries to create new Keyword records again for the ones that it just created in the import and associate those. These probably fail a unique validation at that point, and are not associated nor persisted.
That leaves you with just the unassociated but newly created records.

Rails: Auto-increment column while preserving cardinality

I'm working on an implementing a feature with a scenario similar to this:
Company has many Itemsand each Item has a column called company_item_number.
I'm looking for a way to increment the company_item_number as a new item is added into a particular Company, preserving cardinality even after an item is deleted.
Note that this is different than item_id which will auto increment whenever any item is added by any Company.
For example:
Company A
company_item_number: 1
company_item_number: 2
company_item_number: 3 (removed/deleted)
company_item_number: 4
Company B
company_item_number: 1 (removed/deleted)
company_item_number: 2
As you can see, I also need to make sure that if a previous item was deleted, the next item_number should be +1 greater than the previously deleted item, preserving cardinality.
Any thoughts on how to do this would be greatly appreciated.
This is complicated by your requirement that cardinality be preserved even when records are deleted. I would personally suggest never actually deleting Item records; instead, add a removed_at timestamp column which you can check to decide if an item was removed or not. Then, your approach gets much simpler (for this task, and likely for many other tasks). With that, you could use a before_create hook, which runs right before your new record is saved into the database, to populate the field on each record, like so:
class Item < ActiveRecord::Base
before_create do |item|
current_highest = Item.where(company_id: item.company_id).pluck("MAX(company_item_number)").first
item.company_item_number = (current_highest || 0) + 1
end
end
If you don't want to go that route, your companies.last_item_number_count idea seems like a good option. Alternatively, a new company_item_numbers table from which you never remove records would serve a similar purpose. Ultimately, without some record of the deleted items having been present, there's no way to ensure you aren't re-using a number you already used.

In Ruby/Rails, how can I create one column in a table based on the value of two other columns in the same table?

I am trying to create a Reddit type app where the order of a list depends on a combination of the number of upvotes a link has and the created date. My plan is to create a new column in my "Links" table that combines "created_date" and "upvotes" into a "Rank Value" and then sort the list by the "Rank Value".
Is this the right approach? If so, how do I create this table column using ActiveRecord?
If there is a meta attribute that is used purely for display purposes, creating a method that will generate it on the fly would be appropriate.
If you want to use it for sorting your objects as well, it's better to store it in a column. Hopefully, it doesn't depend on things like the current time, and only on its other attributes:
before_save :calculate_rank
def calculate_rank
self.rank = self.upvotes + self.clicks * 5;
end
Unfortunately, for your use case you specifically said your column depends on the creation date, probably in terms of "how fresh is it" -- a moving target.
You can solve this two ways: by constantly increasing the rank values for newer links indefinitely, or by putting items into time buckets and updating them periodically (degrading their scores when the day or week ends, perhaps).
You can create methods in your model such as "rank_value" which would sort by your criteria and just call Model.rank_value

Ruby on Rails - Strategy on save random invoice #

So, The situation is like this:
I have one table called invoice, and it has a column called invoice #, I have to generate a complex invoice number every time before it was saved to the database.
And besides, I have another table called invoice_item, which will store a mess of items. each item also has a column called invoice # to declare that which invoice the item is belonged.
There is no limit that how many items will be in one invoice.
Now, I have 2 strategies to achieve this:
I have a function called generateCode() to return a random invoice #. I will put it in application_controller.rb, And every time, when we try to insert a new invoice, The method "create" in invoice_controller will generate an new invoice #, and pass the value to all the invoice_items which is belonged to the invoice.
I will use the ActiveRecord callback function: after_initialize, so, when we try to new an invoice instance, the invoice # will be also created, and it will be easy to pass the value to the item list.
It seems that the 2nd way has more logic, but does it have some chance to lead to a performance problem? and in most E-commercial website, the user most likely get their invoice # after they submit the shopping list. so I wanna know that what is the typical way to do this kind of questions, and more important, why?
Thanks
after_initialize operates every time you instantiate an ActiveRecord object, meaning that when you retrieve invoice_items from the database, they will get new invoice numbers (not what you want).
The callback you should use is before_create, which will fire on new objects before they are saved to the database.

Freezing associated objects

Does anyone know of any method in Rails by which an associated object may be frozen. The problem I am having is that I have an order model with many line items which in turn belong to a product or service. When the order is paid for, I need to freeze the details of the ordered items so that when the price is changed, the order's totals are preserved.
I worked on an online purchase system before. What you want to do is have an Order class and a LineItem class. LineItems store product details like price, quantity, and maybe some other information you need to keep for records. It's more complicated but it's the only way I know to lock in the details.
An Order is simply made up of LineItems and probably contains shipping and billing addresses. The total price of the Order can be calculated by adding up the LineItems.
Basically, you freeze the data before the person makes the purchase. When they are added to an order, the data is frozen because LineItems duplicate nessacary product information. This way when a product is removed from your system, you can still make sense of old orders.
You may want to look at a rails plugin call 'AASM' (formerly, acts as state machine) to handle the state of an order.
Edit: AASM can be found here http://github.com/rubyist/aasm/tree/master
A few options:
1) Add a version number to your model. At the day job we do course scheduling. A particular course might be updated occasionally but, for business rule reasons, its important to know what it looked like on the day you signed up. Add :version_number to model and find_latest_course(course_id), alter code as appropriate, stir a bit. In this case you don't "edit" models so much as you do a new save of the new, updated version. (Then, obviously, your LineItems carry a item_id and an item_version_number.)
This generic pattern can be extended to cover, shudder, audit trails.
2) Copy data into LineItem objects at LineItem creation time. Just because you can slap has_a on anything, doesn't mean you should. If a 'LineItem' is supposed to hold a constant record of one item which appeared on an invoice, then make the LineItem hold a constant record of one item which appeared on an invoice. You can then update InventoryItem#current_price at will without affecting your previously saved LineItems.
3) If you're lazy, just freeze the price on the order object. Not really much to recommend this but, hey, it works in a pinch. You're probably just delaying the day of reckoning though.
"I ordered from you 6 months ago and now am doing my taxes. Why won't your bookstore show me half of the books I ordered? What do you mean their IDs were purged when you stopped selling them?! I need to know which I can get deductions for!"
Shouldn't the prices already be frozen when the items are added to the order? Say I put a widget into my shopping basket thinking it costs $1 and by the time I'm at the register, it costs $5 because you changed the price.
Back to your problem: I don't think it's a language issue, but a functional one. Instead of associating the prices with items, you need to copy the prices. If every item in the order has it's own version of a price, future price changes won't effect it, you can add discounts, etc.
Actually, to be clean you need to add versioning to your prices. When an item's price changes, you don't overwrite the price, you add a newer version. The line items in your order will still be associated with the old price.

Resources