Last active
September 12, 2022 18:55
-
-
Save blocknotes/d4f5fde5cd1d49a279f279a45420027d to your computer and use it in GitHub Desktop.
Rails simple versioning
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# db/migrate/20220909010101_create_price_lists.rb | |
class CreatePriceLists < ActiveRecord::Migration[7.0] | |
def change | |
create_table :price_lists do |t| | |
t.string :name, null: false | |
t.integer :version, null: false, default: 1 | |
t.references :source_price_list | |
t.jsonb :price_items_changes | |
t.timestamps | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# db/migrate/20220909010102_create_price_items.rb | |
class CreatePriceItems < ActiveRecord::Migration[7.0] | |
def change | |
create_table :price_items do |t| | |
t.string :code, null: false | |
t.string :name | |
t.decimal :price | |
t.boolean :draft, null: false, default: true | |
t.boolean :deleted, null: false, default: false | |
t.references :price_list | |
t.references :source_item | |
t.timestamps | |
end | |
add_index :price_items, %i[price_list_id draft code], unique: true, order: { draft: :desc, code: :asc } | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/models/price_item.rb | |
class PriceItem < ApplicationRecord | |
belongs_to :price_list | |
belongs_to :source_item, class_name: 'PriceItem', foreign_key: 'source_item_id', optional: true | |
validates :code, presence: true, uniqueness: { | |
scope: %i[price_list_id draft], message: 'must be unique per price list and draft' | |
} | |
validates_presence_of :name, unless: :deleted? | |
validates_presence_of :price, unless: :deleted? | |
def to_s | |
"\"#{name}\" [#{code}] #{price}" | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/models/price_list.rb | |
class PriceList < ApplicationRecord | |
has_many :price_items, -> { where(draft: false) } | |
has_many :draft_price_items, -> { where(draft: true) }, { class_name: 'PriceItem' } | |
has_many :all_price_items, -> { order(draft: :desc, code: :asc) }, { class_name: 'PriceItem', dependent: :destroy } | |
belongs_to :source_price_list, class_name: 'PriceList', foreign_key: 'source_price_list_id', optional: true | |
validates_presence_of :name | |
validates_presence_of :version | |
def create_new_version!(store_changes: true) | |
PriceList.transaction do | |
price_list = PriceList.create!(name: name, source_price_list: self, version: version + 1) | |
logs = _prepare_items(price_list: price_list) | |
logs += _remove_deleted_items | |
update_column(:price_items_changes, logs) if store_changes | |
price_list | |
end | |
end | |
def current_price_items | |
all_price_items.uniq(&:code).reject(&:deleted?) | |
end | |
def dirty? | |
draft_price_items.any? | |
end | |
def update_item!(attrs) | |
if attrs[:source_item].is_a?(PriceItem) | |
draft_item_attrs = attrs[:source_item].slice(*%w[code name price]).symbolize_keys.merge!(attrs) | |
item = draft_price_items.find_by(source_item: attrs[:source_item]) || draft_price_items.build | |
item.update!(draft_item_attrs.except!(:draft, :price_list_id, :source_item_id)) | |
else | |
draft_item_attrs = attrs.slice(*%i[code name price deleted source_item]) | |
draft_price_items.create!(draft_item_attrs.except!(:draft, :price_list_id, :source_item_id)) | |
end | |
end | |
def to_s | |
result = "Price list \"#{name}\" - version #{version}:\n" | |
result << "#{price_items.count} price_items\n" | |
price_items.order(:code).each { |price_item| result << "- #{price_item}\n" } | |
result << "#{draft_price_items.count} draft_price_items\n" | |
draft_price_items.order(:code).each do |price_item| | |
result << "- #{price_item}#{price_item.deleted? ? ' => DELETE' : ''}\n" | |
end | |
result | |
end | |
private | |
def _copy_item(price_list:, price_item:) | |
attrs = price_item.attributes.slice('name', 'code', 'price') | |
price_list.price_items.create!(attrs) | |
nil | |
end | |
def _create_or_update_item(price_list:, price_item:) | |
price_item.update!(price_list: price_list, draft: false) | |
if price_item.source_item | |
{ type: :update, code: price_item.code, price: price_item.price, old_price: price_item.source_item.price } | |
else | |
{ type: :create, code: price_item.code, price: price_item.price } | |
end | |
end | |
def _prepare_items(price_list:) | |
current_price_items.map do |price_item| | |
if price_item.draft? | |
_create_or_update_item(price_list: price_list, price_item: price_item) | |
else | |
_copy_item(price_list: price_list, price_item: price_item) | |
end | |
end.compact | |
end | |
def _remove_deleted_items | |
draft_price_items.destroy_all.map do |item| | |
{ type: :delete, code: item.code } | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Testing: