Commit d6914fdb by ivan Lan

Add model

parent 2d8b8c10
source 'https://rubygems.org' source 'https://gems.ruby-china.org'
# Specify your gem's dependencies in shotengai.gemspec # Specify your gem's dependencies in shotengai.gemspec
gemspec gemspec
class CreateShotengaiProductsAndOrders < ActiveRecord::Migration[5.1]
p 'fuck !'
def change
create_table :shotengai_products do |t|
t.string :title
t.string :status
t.json :spec
t.integer :default_series_id
t.boolean :need_express
t.boolean :need_time_attr
t.string :cover_image
t.json :banners
t.json :detail
# Single Table Inheritance
t.string :type
t.json :meta
t.timestamps
end
create_table :shotengai_series do |t|
t.decimal :original_price, precision: 9, scale: 2
t.decimal :price, precision: 9, scale: 2
t.integer :stock
t.json :spec
# Single Table Inheritance
t.string :type
t.json :meta
t.references :shotengai_products, foreign_key: true
t.timestamps
end
create_table :shotengai_orders do |t|
t.integer :seq
t.string :address
t.datetime :pay_time
t.datetime :delivery_time
t.datetime :receipt_time
t.string :delivery_way
t.string :delivery_cost
t.string :status
t.string :type
t.json :meta
t.integer :buyer_id
t.string :buyer_type
t.timestamps
end
add_index :shotengai_orders, [:buyer_id, :buyer_type]
create_table :shotengai_snapshots do |t|
t.decimal :original_price, precision: 9, scale: 2
t.decimal :price, precision: 9, scale: 2
t.integer :count
t.json :spec
t.json :banners
t.string :cover_image
t.json :detail
# Single Table Inheritance
t.string :type
t.json :meta
t.references :shotengai_series, foreign_key: true
t.references :shotengai_orders, foreign_key: true
t.timestamps
end
end
end
\ No newline at end of file
class CreateShotengaiCatalogs < ActiveRecord::Migration[5.1]
def change
create_table :shotengai_catalogs do |t|
t.string :name
t.string :level_type
t.string :image
# STI
t.string :type
t.references :super_catalog, index: true
t.timestamps
end
end
end
require "shotengai/version" require "shotengai/version"
require 'rails'
require 'active_record'
Dir['./lib/shotengai/*'].each { |f| require f }
module Shotengai module Shotengai
# Your code goes here... # Your code goes here...
......
module Shotengai
module AASM_DLC
require 'aasm'
def self.included(base)
add_event_callbacks
base.include AASM
end
def self.add_event_callbacks
DslHelper::Proxy.class_eval do
def initialize(options, valid_keys, source)
# original
@valid_keys = valid_keys
@source = source # event or transition
@options = options
# dlc
expend_options if AASM::Core::Event === source
end
def expend_options
preset_methods = []
# add callbacks for valid_keys to definite event
# vaild_keys in aasm 4.12.2
# [ :after, :after_commit, :after_transaction, :before,
# :before_transaction, :ensure, :error, :before_success, :success ]
# e.g.
# event :pay ---> :after_pay, after_commit_pay ...
#
@valid_keys.each do |callback|
preset_methods << "#{callback}_#{@source.name}"
@options[callback] = Array(@options[callback]) << "#{callback}_#{@source.name}"
end
# ignore the method missing if it was in preset_methods
@source.class_eval("
def invoke_callbacks(code, record, args)
case code
when String, Symbol
return true if record.respond_to?(code, true).! && code.to_s.in?(#{preset_methods})
end
super(code, record, args)
end
")
end
end
end
end
end
module Shotengai
module Buyer
extend ActiveSupport::Concern
included do
end
class_methods do
def can_shopping_with klass, options={}
unless Shotengai::Order <=> klass # 为子类
raise ArgumentError.new('You can only buy the class inherit from Shotengai::Order')
end
collection_name = klass.model_name.collection || options[:as]
cart_name = "#{klass.model_name.singular}_cart"
# has many Order
has_many collection_name.to_sym, class_name: klass.name, as: :buyer
# has one Cart
has_one cart_name.to_sym, class_name: klass.cart_class.name, as: :buyer
# User.new Cart 相关
class_eval("
def #{cart_name}
super || create_#{cart_name}
end
def add_to_#{cart_name} snapshot
snapshot.update!(test_order_cart: self.#{cart_name})
end
")
end
end
end
end
module Shotengai
# == Schema Information
#
# Table name: shotengai_orders
#
# id :integer not null, primary key
# seq :integer
# address :string(255)
# pay_time :datetime
# delivery_time :datetime
# receipt_time :datetime
# delivery_way :string(255)
# delivery_cost :string(255)
# status :string(255)
# type :string(255)
# meta :json
# buyer_id :integer
# buyer_type :string(255)
# created_at :datetime not null
# updated_at :datetime not null
class Cart < ::ActiveRecord::Base
self.table_name = 'shotengai_orders'
default_scope { where(status: 'cart') }
class << self
def can_buy *good_classes
# 所有snapshot
has_many :snapshots, -> {
where(type: good_classes.map { |good_class| "#{good_class.name}Snapshot" })
}, class_name: 'Shotengai::Snapshot'
good_classes.each do |klass|
# cart has many good_class.collection
has_many klass.model_name.collection.to_sym, class_name: klass.name
# belongs_to 本 Cart class
# optional: true 允许父对象不存在
klass.snapshot_class.belongs_to(
self.model_name.singular.to_sym,
class_name: self.name,
optional: true,
foreign_key: :shotengai_orders_id
)
end
end
end
end
end
module Shotengai
# == Schema Information
#
# Table name: shotengai_catalogs
#
# id :integer not null, primary key
# name :string(255)
# level_type :string(255)
# image :string(255)
# type :string(255)
# super_catalog_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Catalog < ActiveRecord::Base
self.table_name = 'shotengai_catalogs'
validates_presence_of :name
class << self
def inherited subclass
subclass.has_many :sub_catalogs, class_name: subclass.name, foreign_key: :super_catalog_id
subclass.belongs_to :super_catalog, class_name: subclass.name, optional: true
# subclass.instance_eval("def klass; #{subclass}; end")
super
end
def top_catalogs
where(super_catalog: nil)
end
def validate_name_chain name_ary, order='desc'
ary = order.downcase.eql?('asc') ? name_ary.reverse : name_ary
where(name: ary.last).each do |bottom_catalog|
return name_ary if bottom_catalog.name_chain.eql?(ary)
end
raise Shotengai::WebError.new('Illegality catalge name chain', '-1', '400')
end
# def input_from_file
# end
end
def ancestors
ary = [self]
ary.unshift(ary.first.super_catalog) until ary.first.super_catalog.nil?
ary
end
def nest_level
ancestors.count
end
def name_chain
ancestors.map(&:name)
end
def brothers
super_catalog.sub_catalogs
end
end
end
module Shotengai
# == Schema Information
#
# Table name: shotengai_orders
#
# id :integer not null, primary key
# seq :integer
# address :string(255)
# pay_time :datetime
# delivery_time :datetime
# receipt_time :datetime
# delivery_way :string(255)
# delivery_cost :string(255)
# status :string(255)
# type :string(255)
# meta :json
# buyer_id :integer
# buyer_type :string(255)
# created_at :datetime not null
# updated_at :datetime not null
class Order < ActiveRecord::Base
self.table_name = 'shotengai_orders'
belongs_to :buyer, polymorphic: true
default_scope { where.not(status: 'cart') }
include AASM_DLC
aasm column: :status do
state :unpaid, initial: true
state :paid, :delivering, :received, :evaluated
{
pay: { from: :unpaid, to: :paid, after: :fill_snapshot },
cancel: { from: :unpaid, to: :canceled },
send_out: { from: :paid, to: :delivering, after: :set_delivery_time },
get_it: { from: :delivering, to: :received, after: :set_receipt_time },
evaluate: { from: :received, to: :evaluated },
# soft_delete: { from: : to: :evaluated },
}.each { |name, options|
event(name) { transitions options }
}
end
def fill_snapshot
ActiveRecord::Base.transaction {
self.snapshots.each(&:copy_info)
}
end
def set_delivery_time
update!(delivery_time: Time.zone.now)
end
def set_receipt_time
update!(receipt_time: Time.zone.now)
end
def total_price
snapshots.sum(&:total_price)
end
def total_original_price
snapshots.sum(&:total_original_price)
end
class << self
def inherited subclass
# define Cart class
subclass.instance_eval("
def cart_class; ::#{subclass}::Cart; end
class ::#{subclass}::Cart < Shotengai::Cart
def order_klass; #{subclass}; end
end
")
super
end
def can_buy *good_classes
# 所有snapshot
has_many :snapshots, -> {
where(type: good_classes.map { |good_class| "#{good_class.name}Snapshot" })
}, class_name: 'Shotengai::Snapshot',
foreign_key: :shotengai_orders_id
good_classes.each do |klass|
has_many(
klass.snapshot_class.model_name.collection.to_sym,
class_name: klass.snapshot_class.name,
foreign_key: :shotengai_orders_id
)
# optional: true 允许父对象不存在
klass.snapshot_class.belongs_to(
self.model_name.singular.to_sym,
class_name: self.name,
optional: true,
foreign_key: :shotengai_orders_id
)
end
self.cart_class.can_buy *good_classes
end
end
end
end
module Shotengai
# == Schema Information
#
# Table name: shotengai_products
#
# id :integer not null, primary key
# title :string(255)
# status :string(255)
# spec :json
# default_series_id :integer
# need_express :boolean
# need_time_attr :boolean
# cover_image :string(255)
# banners :json
# detail :json
# type :string(255)
# meta :json
# created_at :datetime not null
# updated_at :datetime not null
class Shotengai::Product < ActiveRecord::Base
self.table_name = 'shotengai_products'
validate :check_spec, if: :spec
include AASM_DLC
aasm column: :status do
state :no_on, initial: true
state :on_sale, :deleted
event :put_on_shelf { transitions from: :no_on, to: :on_sale }
event :sold_out { transitions from: :on_sale, to: :no_on }
event :soft_delete { transitions from: [:on_sale, :no_on], to: :deleted }
end
def default_series
Shotengai::Series.find_by_id(default_series_id) || series.first
end
class << self
# TODO: ::#{subclass}Series 增加命名规则定义 降低耦合性??
def inherited(subclass)
# 创建相关 series 与 snapshot
define_related_class(subclass)
add_associations(subclass)
super
end
def define_related_class subclass
# Useing Class.new could not get class_name in self.inherited
class_eval("
class ::#{subclass}Series < Shotengai::Series; end;
class ::#{subclass}Snapshot < Shotengai::Snapshot; end
")
subclass.instance_eval do
def series_class; Object.const_get "#{self.name}Series" ; end
def snapshot_class; Object.const_get "#{self.name}Snapshot"; end
end
end
def add_associations subclass
subclass.has_many :series, class_name: subclass.series_class.name, foreign_key: 'shotengai_products_id'
subclass.has_many :snapshots, class_name: subclass.snapshot_class.name, through: :series, source: :snapshots
end
# Will get methods:
# "#{tag_name}_list" tag_name is singular
# tagger_with('xx', on: "#{tag_name}.to_sym): tag_name is plural
def join_catalog_system catalog_class, options={}
tag_name = options[:as] || catalog_class.model_name.collection
acts_as_taggable_on tag_name.to_sym
# 只有完整替换(只属于一个分类)的时候才进行验证,add remove 暂时未添加
class_eval do
define_method("#{tag_name}_list=") { |value|
super catalog_class.validate_name_chain(value)
}
end
end
end
private
# spec 字段
def check_spec
raise Shotengai::WebError.new('spec 必须是个 Hash', '-1', 400) unless spec.is_a?(Hash)
spec.values { |val| raise Shotengai::WebError.new('值必须为 Array', '-1', 400) unless val.is_a?(Array) }
end
end
end
module Shotengai
# == Schema Information
#
# Table name: shotengai_series
#
# id :integer not null, primary key
# original_price :decimal(9, 2)
# price :decimal(9, 2)
# stock :integer
# spec :json
# type :string(255)
# meta :json
# shotengai_products_id :integer
# created_at :datetime not null
# updated_at :datetime not null
class Series < ActiveRecord::Base
self.table_name = 'shotengai_series'
validate :check_spec, if: :spec
delegate :detail, :banners, :cover_image, to: :product
class << self
def inherited subclass
@subclass = subclass
@product_name = /^(.+)Series$/.match(subclass.name)[1]
add_associations
super
end
def add_associations
# belongs to Product
@subclass.belongs_to :product, foreign_key: :shotengai_products_id, class_name: @product_name
@subclass.belongs_to @product_name.underscore.to_sym, foreign_key: :shotengai_products_id, class_name: @product_name
# has many snapshot
@subclass.has_many :snapshots, class_name: "#{@product_name}Snapshot", foreign_key: :shotengai_series_id
end
end
private
# spec 字段
def check_spec
raise Shotengai::WebError.new('spec 必须是个 Hash', '-1', 400) unless spec.is_a?(Hash)
raise Shotengai::WebError.new('非法的关键字,或关键字缺失', '-1', 400) unless (product.spec.keys - spec.keys).empty?
illegal_values = {}
spec.each { |key, value| illegal_values[key] = value unless value.in?(product.spec[key]) }
# p Shotengai::WebError.new("非法的值,#{illegal_values}", '-1', 422)
raise Shotengai::WebError.new("非法的值,#{illegal_values}", '-1', 400) unless illegal_values.empty?
end
end
end
module Shotengai
# == Schema Information
#
# Table name: shotengai_snapshots
#
# id :integer not null, primary key
# original_price :decimal(9, 2)
# price :decimal(9, 2)
# count :integer
# spec :json
# banners :json
# cover_image :string(255)
# detail :json
# type :string(255)
# meta :json
# shotengai_series_id :integer
# shotengai_orders_id :integer
# created_at :datetime not null
# updated_at :datetime not null
class Snapshot < ActiveRecord::Base
self.table_name = 'shotengai_snapshots'
validate :check_spec, if: :spec
validates :count, numericality: { only_integer: true, greater_than: 0 }
class << self
def inherited subclass
product_name = /^(.+)Snapshot/.match(subclass.name)[1]
series_name = "#{product_name}Series"
# belongs to Series
subclass.belongs_to :series, foreign_key: :shotengai_series_id, class_name: series_name
subclass.belongs_to series_name.underscore.to_sym, foreign_key: :shotengai_series_id, class_name: series_name
super
end
end
# QUESTION: spec 赋值是在 after pay 合理?
# 支付前 信息 delegate to series
[:original_price, :price, :spec, :banners, :cover_image, :detail].each do |column|
define_method(column) { read_attribute(column) || self.series.read_attribute(column) }
end
# 订单支付后 存储当时信息快照
def copy_info
self.update!(
original_price: series.original_price,
price: series.price,
spec: series.spec,
banners: series.banners,
cover_image: series.cover_image,
detail: series.detail,
meta: series.product.meta.merge(series.meta)
)
end
def meta
read_attribute(:meta) || series.product.meta.merge(series.meta)
end
###### view
def total_price
count * price
end
def total_original_price
count * original_price
end
######
private
# spec 字段
def check_spec
raise Shotengai::WebError.new('spec 必须是个 Hash', '-1', 400) unless spec.is_a?(Hash)
raise Shotengai::WebError.new('非法的关键字,或关键字缺失', '-1', 400) unless (series.product.spec.keys - spec.keys).empty?
illegal_values = {}
spec.each { |key, value| illegal_values[key] = value unless value.in?(series.product.spec[key]) }
# p Shotengai::WebError.new("非法的值,#{illegal_values}", '-1', 422)
raise Shotengai::WebError.new("非法的值,#{illegal_values}", '-1', 400) unless illegal_values.empty?
end
end
end
\ No newline at end of file
module Shotengai
class WebError < RuntimeError
attr_accessor :message, :code, :status
def initialize message, code, status
@message, @code, @status = message, code, status
end
end
end
...@@ -4,31 +4,42 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) ...@@ -4,31 +4,42 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'shotengai/version' require 'shotengai/version'
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "shotengai" spec.name = 'shotengai'
spec.version = Shotengai::VERSION spec.version = Shotengai::VERSION
spec.authors = ["ivan Lan"] spec.authors = ['ivan Lan']
spec.email = ["mumumumushu@gmail.com"] spec.email = ['mumumumushu@gmail.com']
spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.} spec.summary = %q{}
spec.description = %q{TODO: Write a longer description or delete this line.} spec.description = %q{}
spec.homepage = "TODO: Put your gem's website or public repo URL here." spec.homepage = 'https://git.tallty.com/open-source/shotengai'
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host. # to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata) if spec.respond_to?(:metadata)
spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" spec.metadata['allowed_push_host'] = 'https://git.tallty.com/open-source/shotengai'
else else
raise "RubyGems 2.0 or newer is required to protect against " \ raise 'RubyGems 2.0 or newer is required to protect against ' \
"public gem pushes." 'public gem pushes.'
end end
spec.files = `git ls-files -z`.split("\x0").reject do |f| spec.files = 'git ls-files -z'.split('\x0').reject do |f|
f.match(%r{^(test|spec|features)/}) f.match(%r{^(test|spec|features)/})
end end
spec.bindir = "exe" spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"] spec.require_paths = ['lib']
spec.add_dependency 'aasm'
spec.add_dependency 'acts-as-taggable-on'
spec.add_dependency 'rails'
spec.add_development_dependency "bundler", "~> 1.14" spec.add_development_dependency 'bundler', '~> 1.14'
spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency 'factory_girl_rails'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency "rspec", "~> 3.0"
# spec.add_development_dependency 'rspec-rails-swagger', git: 'git://github.com/tallty/rspec-rails-swagger'
# spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'mysql2'
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment