Database - Should total price be stored in a column? - ruby-on-rails

I have orders and items table. Here's the association
# order.rb
has_many :items
# item.rb
belongs_to :order
What I'm wondering is: Should I keep the total price in the order table? The total price is summed from each item.
I need to generate Chart and the only data needed is the total price.
My app is still on local development so I haven't had any performance issue. But I'm guessing it will start having an impact after there are few hundreds of orders.

I suggest that you keep the total price in the orders table as well.
One common thing about shops is that prices change. And when they change they create a huge mess (in both accounting and stats) unless you keep them somewhere other than the product itself.
There is no need to worry about performance for that thing.

Related

Is it a good idea to serialize immutable data from an association?

Let's say we have a collection of products, each with their own specifics e.g. price.
We want to issue invoices that contain said products. Using a direct association from Invoice to Product via :has_many is a no-go, since products may change and invoices must be immutable, thus resulting in an alteration of the invoice price, concept, etc.
I first thought of having an intermediate model like InvoiceProduct that would be associated to the Invoice and created from a Product. Each InvoiceProduct would be unique to its parent invoice and immutable. This option would increase the db size significantly as more invoices get issued though, so I think it is not a good option.
I'm now considering adding a serialized field to the invoice model with all the products information that are associated to it, a hash of the collection of items the invoice contains. This way we can have them in an immutable manner even if the product gets modified in the future.
I'm not sure of possible mid or long term downsides to this approach, though. Would like to hear your thoughts about it.
Also, if there's some more obvious approach that I might have overlooked I'd love to hear about it too.
Cheers
In my experience, the main downside of a serialized field approach vs the InvoiceProducts approach described above is decreased flexibility in terms of how you can use your invoice data going forward.
In our case, we have Orders and OrderItems tables in our database and use this data to generate sales analytics reports as well as customer Invoices.
Querying the OrderItem data to generate the sales reports we need is much faster and easier with this approach than it would be if the same data was stored as serialized data in the db.
No.
Serialized columns have no place in a modern application. They are a overused dirty hack from the days before native JSON/JSONB columns were widespread and have only downsides. The only exception to this rule is when you're using application side encryption.
JSON/JSONB columns can be used for a limited number of tasks where the data defies being defined by a fixed schema or if you're just storing raw json responses - but it should not be how you're defining your schema out of convenience because you're just shooting yourself in the foot. Its a special tool for special jobs.
The better alternative is to actually use good relational database design and store the price at the time of sale and everything else in a separate table:
class Order < ApplicationRecord
has_many :line_items
end
# rails g model line_item order:belongs_to product:belongs_to units:decimal unit_price:decimal subtotal:decimal
# The line item model is responsible for each item of an order
# and records the price at the time of order and any discounts applied to that line
class LineItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :line_items
end
A serialized column is not immutable in any way - its actually more prone to denormalization and corruption as there are no database side constraints to ensure its correctness.
Tables can actually be made immutable in many databases by using triggers.
Advantages:
No violation of 1NF.
A normalized fixed data schema to work with - constraints ensure the validity of the data on the database level.
Joins are an extremely powerful tool and not as expensive as you might think.
You can actually access and make sense of the data outside of the application if needed.
DECIMAL data types. JSON/JSONB only has a single number type that uses IEEE 754 floating point.
You have an actual model and assocations instead of having to deal with raw hashes.
You can query the data in sane queries.
You can generate aggregates on the database level and use tools like materialized views.

Should I use a model archive in rails

I have a model product with a has_many relation prices. The prices table is growing rapidly, only few current prices are normally needed, but I want to keep all as a history.
So I am thinking to "archive" all old prices. How do I do that best?
Before I had a column old and was filtering them out when ever I only wanted the current prices. But now the prices table has 2.5 million rows and only 200k are needed in most situations. That's why I thought I would just create a new model price_archive. Copy all "old" prices to price_archive and delete it from prices. And all logic will be moved to a module, used by both models, so I can use price and price_archive in the same way.
Pros for the archive approach:
~ most of the queries are done on the smaller data set (200k, not much growing)
Cons:
displaying both ordered by time needs to be sorted on some kind of joined data set, because times overlap. So it looks like (part.prices.to_a + part.prices_archive.to_a).sort(&:time). Not a big problem, because this will be used very soldomly. But:
I have other models (i.e. order) that use prices in a belongs_to relation, so those need price_id and price_archive_id (with one id always being nil), so that they still reference a price.
Most queries are: show all prices for product (in a select box) and mark the price that is connected to this order (or add it to the select box, when it is archived)
So the code would be something like:
Order.where(*where*).includes(:part => :prices, :price, :price_archive)
The db will query: prices WHERE part_id = ? [on 200k] + prices WHERE id = ? [on 200k] + price_archives WHERE id = ? [on 2300k, but with primary_key]
instead of prices WHERE part_id = ? [on 2500k, with normal index]
Is there a better way or should I stay with the old column?

Rails 5.2 - How to implement quantity availability check with cart booking app?

Im building a Golf Cart rental app where Users can Book Golf Carts, what I need help with is implementing a Quantity and Availability check where when a cart is booked, the quantity is subtracted by 1 UNTIL the booking end date, and if quantity is currently 0 the cart is considered unavailable and unbookable. Im using Rails 5.2,
I have a Devise User model with added first and last name. A Cart model with name, description, quantity, and price columns. And a Booking model with start date, end date, user_id and cart_id columns. A User has_many bookings, a cart has_many bookings, and a booking belongs_to User.
I've been trying it on my own for days and no answers on here take into account the quantity aspect that I'm looking for.
This is a good example of a problem that could be solved either by computing values or by storing them in a database. I do not think you want to store "quantity" or "availability" in a database structure.
Per my comments, you should consider storing your "Carts" model (which I would rename "CartTypes") in memory. The only reason I can see to store it in a database table would be if you need to update the characteristics frequently--for example, if carts often break down and need to be fixed, you would need to change the total available carts for a given type.
In either case, you would have a "total_available" attribute for your CartType model. I would definitely not store a "quantity" attribute. Instead, you'll want to look at your bookings, determine the cart_type, start_date, and end_date, count the bookings for a particular cart_type on a given date, and subtract that number from total_available to determine availability.
How you write the query to do this depends on your schema and relationships. If you can be more specific, then I can provide a specific query.

database design for composite elements

I'm building a site that tracks donations and sales of items in a school auction.
Items can be sold individually or in lots, which are just groups of items bundled for sale as a single unit (like a gift certificate for a dinner Item bundled with a gift certificate for movie tickets Item).
Both of these things (Items and Lots) share fields like name, description, value. But Items have additional fields, like the donor, restrictions of use, type of item, etc.
I started by creating a table called Lot and an association table that lets Lots contain 1+ Items.
That works great for Lots. But that leaves me with a problem:
When Buyers win I need to record the win and the price. I'm doing that with a Win table that associates the Buyer with the Lot and the winning price.
But how do I deal with all the Items that aren't assigned to Lots? Should every item be in a Lot, just singly? That would make sense because it would work with the Win table scheme above, but I would need to automatically create a Lot for every Item that isn't already in another Lot. Which seems weird.
I'm sure this is a simple problem, but I can't figure it out!
Thanks!
Your approach of treating every item as a lot should be the winning one. It may sound weird, but it will make things way easier in the long run.
I have to deal on a daily base with a database where a similar problem was 'solved' the other way round, meaning keeping bundles of items and items apart and that proved to be a great pita (and for sure I'm not talking about a flat round bread here).
This database is both backbone for statistical evaluations and a bunch of (web) applications and on countless occasions I run into trouble when deciding which table to chose or how to level the differences between those two groups in querying and in coding.
So, even if your project will be rather small eventually, that is a good idea.
Yes, you need to provide a method to put every item in a lot, but this trouble is to be taken just once. On the other hand your queries wouldn't become significantly more complex because of that 'extra' table, so I'd definitely would chose this way.
It sounds like you have an Auction model that could have one or many Items. Then you could have two different types of Auctions, Auction::Single and Auction::Lot. Price would be a column on Auction. Auction has many Bids which is a join model between the Auction and the User (or Bidder). That join model would also store the bid price. When the Auction is over, you could create a separate Win record if you want, or just find the Winner through the highest Bid from Auction.
It would be helpful if you showed some code. But, what you want is a polymorphic association. So, something like:
class Item
has_one :win, as: :winnable
belongs_to :lot
end
class Lot
has_one :win, as: :winnable
has_many :items
end
class Win
belongs_to :buyer
belongs_to :winnable, polymorphic: true
end

How to model "products" in an online store application

I'm building an online store to sell products like "Green Extra-large, T-shirts". I.e., the same shirt can have many sizes / colors, different combination can be sold out, different combination might have different prices, etc.
My question is how I should model these products in my Rails application (or really how to do it in any application).
My current thinking is:
Class Product
has_many :variants, :through => :characteristics
has_many :characteristics
end
Class Characteristic
belongs_to :product
belongs_to :variants
end
Class Variant
has_many :products, :through => :characteristics
belongs_to :characteristic
end
So each product will have one or more characteristics (e.g., "Color", "Size", etc), and each characteristic will then have one or more variants (e.g., "Red", "Blue", etc).
The problem with this method is where do I store price and inventory? I.e., a given product's price and inventory are determined by the variants its characteristics take. (Green might be more expensive than red, large might be out of stock, etc).
One thought I had was to give products a "base_price", and let variants modify it, but this seems overly complex (and might not work).
I have seen two solutions to this kind of dilemma. The first is to try to use characteristics to define subordinate products to the "main" product. The challenge here is that in addition to your thoughts for far, in most cases the product will evolve with new manufacturers that bring new aspects to the table. For example, one manufacturer may make a cheaper product, but have a different application method for the logo or stitching that may be significant enough to track.
I think that carrying a non significant product number for each product and then attaching the characteristics as attributes works out the best. It is easily searched and extensible. If a group of products are strongly related, a ProductGroup that the individual products attach to works well.
In tables:
ProductGroup
--------------------
ProductGroupID
ProductGroupName
ProductGroupDescription
Product
--------------------
ProductID
ProductGroupID
QtyOnHand
BasePrice
ProductColorID
ProductSizeID
ProductColor
------------
ProductColorID
ProductColorName
ProductSize
--------------
ProductSizeID
ProductSizeName
...more attributes...
The advantages here are that you can easily query for specific attributes, attributes are "flexible" in that more can be added (and old ones adjusted: if you started with "Red" but then added another "Red" to the color pool, you can change them to "Maroon" and "Bright Red".
You can control price and inventory are at the detail product level (although more tables may be required to account for sourcing costs).
This all assumes that your characteristics are universally shared. If they are not, your characteristic subtable approach can work by creating a join table between characteristics and the product detail tables and populate as needed. This will require more business logic .to ensure each product category gets all characteristics necessary. In this latter case I would use "prototype" products in the base product table (with Qty and Cost of 0) that I would clone the characteristics from and then adjust as each new product is entered. As you move forward, when a new variation appears, having a "clone this product" function that allows you to just adjust the differences from the base product would be valuable.
Finally, as far as managing the inventory and pricing, this is going to happen at the UI layer. Being able to generate queries for related products (product groups) and manage all the pricing for related products will go a long way to making this livable.
Just a quick-note. You can always try and take a look at the sourcecode of some other e-commerce products like Spree and Substruct they probably already answered that question for you.

Resources