Commit 162a7325 by ivan Lan

Improve the Harray & Improve json column format

parent 4a4d7457
......@@ -39,9 +39,10 @@ module Shotengai
private
def resource_params
spec_value = params.require(resource_key).fetch(:spec_value, nil)&.permit!
info_value = params.require(resource_key).fetch(:info_value, nil)&.permit!
remark_value = params.require(resource_key).fetch(:remark_value, nil)&.permit!
spec_value = params.require(resource_key).fetch(:spec_value, nil)&.map(&:permit!)
info_template = params.require(resource_key).fetch(:info_template, nil)&.map(&:permit!)
detail_info_template = params.require(resource_key).fetch(:detail_info_template, nil)&.map(&:permit!)
remark_value = params.require(resource_key).fetch(:remark_value, nil)&.map(&:permit!)
meta = params.require(resource_key).fetch(:meta, nil)&.permit!
# ????????!!!!!, spec_value: [:key, :val] 一样的输出值 却在test报错???
# QUESTION: WARNING: 文档bug吧?????
......@@ -49,7 +50,8 @@ module Shotengai
:original_price, :price, :stock#, spec_value: [:key, :val]
).merge(
{
spec_value: spec_value, info_value: info_value, remark_value: remark_value,
spec_value: spec_value, info_template: info_template, remark_value: remark_value,
detail_info_template: detail_info_template,
meta: meta
}
)
......
......@@ -65,7 +65,6 @@ module Shotengai
# spec = params.require(resource_key).fetch(:spec, nil).try(:permit!)
spec_template = params.require(resource_key).fetch(:spec_template, nil)&.map(&:permit!)
remark_template = params.require(resource_key).fetch(:remark_template, nil)&.map(&:permit!)
info_template = params.require(resource_key).fetch(:info_template, nil)&.map(&:permit!)
detail = params.require(resource_key).fetch(:detail, nil)&.permit!
meta = params.require(resource_key).fetch(:meta, nil)&.permit!
# NOTE: :catalog_list is a default catalog list for template example, maybe should move it to the template controller, but it need add controller template for every controller
......@@ -78,7 +77,6 @@ module Shotengai
).merge(
{
spec_template: spec_template, remark_template: remark_template,
info_template: info_template,
detail: detail, meta: meta
}
)
......
module Shotengai
class Harray < Array
class << self
def encode hash
Harray.new(
hash.map {|key, val| { 'key' => key, 'val' => val } }
)
end
def decode harray
harray && Harray.new(harray).decode
end
end
# def initialize
# # Add some validations
# end
def keys
self.map { |obj| obj['key'] }
end
......@@ -12,5 +28,13 @@ module Shotengai
self.each { |obj| return obj['val'] if obj['key'].eql?(key) }
nil
end
def decode
self.map{ |obj| { obj['key'] => obj['val'] } }.reduce(&:merge)
end
def hash_map
self.map { |obj| yield obj['key'], obj['val']}
end
end
end
......@@ -4,96 +4,170 @@ module Shotengai
included do
end
class_methods do
def custom_hash_columns columns, options={}
decode_or_not = options[:decode]
class_methods do
# Option decode: true means storing as hash
def harray_setter *columns, decode: false
columns.each do |column|
# QUESTION: 这样可以避免 send("#{column}="), 合适?
class_eval %Q{
define_method('#{column}') do
super() || {}
end
if #{decode_or_not}
define_method("#{column}_input=") do |val|
parsed_val = val && val.map{ |h| { (h[:key] || h['key']) => (h[:val] || h['val']) } }.reduce(&:merge)
self.#{column} = parsed_val
end
define_method("#{column}_output") do
self.#{column}.map {|key, val| { key: key, val: val } }
end
def #{column}= val
raise Shotengai::WebError.new('#{column} 必须是个 Array', -1 , 401) unless val.nil? || Array === val
super(#{decode} ? Harray.decode(val) : val)
end
}
end
end
def hash_column
# like meta, detail these json using for code development
end
def generate_hash_template_column_for *names
names.each do |name|
class_eval %Q{
def #{name}_template
Shotengai::Harray.new(super() || [])
end
def #{name}_template= val
raise Shotengai::WebError.new('#{name}_val 必须是个 Array', -1 , 401) unless val.nil? || Array === val
super(val)
def harray_getter *columns, decode: false
columns.each do |column|
class_eval %{
def #{column}
#{decode} ?
Shotengai::Harray.encode(super() || []) :
Shotengai::Harray.new(super() || [])
end
}
end
end
def generate_hash_value_column_for *names, delegate_template_to: nil
names.each do |name|
class_eval %Q{
delegate :#{name}_template, to: :#{delegate_template_to}
def #{name}
{
template: self.#{name}_template.map { |x| x['key'] },
value: self.#{name}_value,
}
end
def harray_accessor *columns, decode: false
harray_getter *columns, decode: decode
harray_setter *columns, decode: decode
end
def #{name}_value= val
raise Shotengai::WebError.new('#{name}_val 必须是个 Hash', -1 , 401) unless val.nil? || Hash === val
super(val)
end
}
def template_with_value key, value: "#{key}_value", template: "#{key}_template"
class_eval %Q{
def #{key}
{
template: #{template},
value: #{value},
}
end
}
end
def template_with_value_getters *keys, value_in_template: false, delegate_template_to: nil
if delegate_template_to
self.delegate(*keys.map { |key| "#{key}_template" }, to: delegate_template_to)
end
keys.each do |key|
value = value_in_template ? "#{key}_template.decode" : "#{key}_value"
self.template_with_value key, value: value
end
end
def column_has_children column, options
ArgumentError.new("Please give #{column} one child at least.") unless options[:children]
children_names = options[:children].map(&:to_s)
self_name = options[:as] || self.model_name.singular
def column_has_implants column, implants: nil, as: 'host', prefix: 'full_'
ArgumentError.new("Please give #{column} one child at least.") unless implants
ArgumentError.new('Duplicate value in option :implants with option :as') if (Array(implants) & Array(as)).any?
chimera = "#{prefix}#{column}"
class_eval %Q{
define_method('full_#{column}') do
define_method('#{chimera}') do
read_attribute(:#{column}) || {}
end
define_method('full_#{column}=') do |val|
define_method('#{chimera}=') do |val|
raise Shotengai::WebError.new('#{chimera} 必须是个 Hash', -1 , 401) unless val.nil? || Hash === val
write_attribute(:#{column}, val)
end
def #{column}_before_implant
#{column}
end
define_method('#{column}') do
full_#{column}['#{self_name}'] || {}
#{chimera}['#{as}'] || {}
end
def #{column}_before_implant= val
#{column}= val
end
define_method('#{column}=') do |val|
val = super(val)
self.full_#{column} = full_#{column}.merge('snapshot' => val)
val = #{column}_before_implant=(val)
self.#{chimera} = #{chimera}.merge('#{as}' => val)
end
#{children_names}.each do |child|
#{Array(implants)}.each do |child|
define_method(\"\#{child}_#{column}\") do
full_#{column}[child]
#{chimera}[child]
end
# WARNING: TODO: 这里 val = 并没有继承
define_method("\#{child}_#{column}=") do |val|
self.#{chimera} = #{chimera}.merge(child => val)
end
end
}
end
# TODO:ORNOT:
def hash_column
# like meta, detail these json using for code development
end
# def custom_hash_columns columns, options={}
# decode_or_not = options[:decode]
# columns.each do |column|
# # QUESTION: 这样可以避免 send("#{column}="), 合适?
# class_eval %Q{
# define_method('#{column}') do
# super() || {}
# end
# if #{decode_or_not}
# define_method("#{column}_input=") do |val|
# parsed_val = val && val.map{ |h| { (h[:key] || h['key']) => (h[:val] || h['val']) } }.reduce(&:merge)
# self.#{column} = parsed_val
# end
# define_method("#{column}_output") do
# self.#{column}.map {|key, val| { key: key, val: val } }
# end
# end
# }
# end
# end
# def generate_hash_template_column_for *names
# names.each do |name|
# class_eval %Q{
# def #{name}
# {
# template: self.#{name}_template.map { |x| x['key'] },
# value: self.#{name}_value,
# }
# end
# def #{name}_template
# Shotengai::Harray.new(super() || [])
# end
# redef #{name}_template= val
# raise Shotengai::WebError.new('#{name}_val 必须是个 Array', -1 , 401) unless val.nil? || Array === val
# old(val)
# end
# }
# end
# end
# def generate_hash_value_column_for *names, delegate_template_to: nil
# names.each do |name|
# class_eval %Q{
# delegate :#{name}_template, to: :#{delegate_template_to}
# def #{name}
# {
# template: self.#{name}_template.map { |x| x['key'] },
# value: self.#{name}_value,
# }
# end
# def #{name}_value= val
# raise Shotengai::WebError.new('#{name}_val 必须是个 Hash', -1 , 401) unless val.nil? || Hash === val
# old(val)
# end
# }
# end
# end
end
end
end
......@@ -20,7 +20,6 @@ module Shotengai
# created_at :datetime not null
# updated_at :datetime not null
# remark_template :json
# info_template :json
#
# Indexes
#
......@@ -32,10 +31,13 @@ module Shotengai
require 'acts-as-taggable-on'
self.table_name = 'shotengai_products'
generate_hash_template_column_for :spec, :info, :remark
# generate_hash_template_column_for :spec, :remark
harray_accessor :spec_template, :remark_template
template_with_value_getters :spec, :remark, value_in_template: true
belongs_to :manager, polymorphic: true, optional: true#, touch: true
default_scope { order(created_at: :desc) }
scope :alive, -> { where.not(status: 'deleted') }
scope :recycle_bin, ->{ unscope(where: :status).deleted.where('updated_at > ?', Time.now - 10.day )}
......
......@@ -15,7 +15,7 @@ module Shotengai
# updated_at :datetime not null
# aasm_state :string(255)
# remark_value :json
# info_value :json
# info_template :json
#
# Indexes
#
......@@ -36,9 +36,19 @@ module Shotengai
# validate remark
validate :check_remark_value, unless: :remark_template_empty?
harray_accessor :info_template, :detail_info_template
harray_accessor :spec_value, :remark_value, decode: true
template_with_value_getters :info, value_in_template: true
template_with_value_getters :spec, :remark, delegate_template_to: :product
# info_template
# generate_hash_template_column_for :info
# full_info_template: { optional: d, detail: detail_info_template }
column_has_implants :info_template, implants: ['detail'], as: 'optional'
generate_hash_value_column_for :spec, :info, :remark, delegate_template_to: :product
# generate_hash_value_column_for :spec, :remark, delegate_template_to: :product
delegate :title, :detail, :banners, :cover_image, :status, :status_zh, :manager, to: :product
scope :alive, -> { where.not(aasm_state: 'deleted') }
......@@ -48,7 +58,7 @@ module Shotengai
scope :query_spec_value_with_product, ->(val, product) {
if val.keys.sort == product.spec_template.keys.sort
keys = []; values = []
val.map { |k, v| keys << "spec_value->'$.\"#{k}\"' = ? "; values << v }
val.hash_map { |k, v| keys << "spec_value->'$.\"#{k}\"' = ? "; values << v }
where(product: product).where(keys.join(' and '), *values)
else
self.none
......@@ -109,15 +119,14 @@ module Shotengai
private
# spec 字段
def spec_template_empty?
spec_template.empty? && spec_value.nil? # 当且仅当二者都为空才跳过验证
spec_template.empty? && spec_value.empty? # 当且仅当二者都为空才跳过验证
end
def remark_template_empty?
remark_template.empty? && remark_value.nil? # 当且仅当二者都为空才跳过验证
remark_template.empty? && remark_value.empty? # 当且仅当二者都为空才跳过验证
end
def check_spec_value
errors.add(:spec_value, 'spec_value 必须是个 Hash') unless spec_value.is_a?(Hash)
errors.add(:spec_value, '非法的关键字,或关键字缺失') unless (product.spec_template.keys - spec_value.keys).empty?
illegal_values = {}
spec_value.each { |key, value| illegal_values[key] = value unless value.in?(product.spec_template.val_at(key) || []) }
......@@ -135,7 +144,6 @@ module Shotengai
end
def check_remark_value
errors.add(:remark_value, 'remark_value 必须是个 Hash') unless remark_value.is_a?(Hash)
# product.remark_value.keys 包含 remark_value.keys
illegal_key = (remark_value.keys - product.remark_template&.keys)
errors.add(:remark_value, "非法的关键字, #{illegal_key}") unless illegal_key.empty?
......
......@@ -38,10 +38,11 @@ module Shotengai
validate :check_remark_value, unless: :remark_template_empty?
validates :count, numericality: { only_integer: true, greater_than: 0 }
generate_hash_value_column_for :spec, :info, :remark, delegate_template_to: :series
# generate_hash_value_column_for :spec, :info, :remark, delegate_template_to: :series
template_with_value_getters :spec, :remark, :info, delegate_template_to: :series
column_has_children :meta, children: ['product', 'series'], as: :snapshot
column_has_children :info_value, children: ['series'], as: :snapshot
column_has_implants :meta, implants: ['product', 'series'], as: :snapshot
column_has_implants :info_value, implants: ['detail'], as: :snapshot
validate :cannot_edit, if: :order_was_paid
before_destroy :cannot_edit, if: :order_was_paid
......@@ -112,15 +113,11 @@ module Shotengai
banners: series.banners,
cover_image: series.cover_image,
detail: series.detail,
full_meta: {
product: product.meta,
series: series.meta,
snapshot: meta,
},
full_info_value: {
series: series.info_value,
snapshot: info_value,
}
product_meta: product.meta,
series_meta: series.meta,
meta: meta,
detail_info_value: series.detail_info_template,
info_value: info_value,
)
end
......@@ -159,7 +156,7 @@ module Shotengai
end
def check_remark_value
nullable_keys = series.remark_value.select{ |k, v| v }.keys
nullable_keys = series.remark_value.decode.select{ |k, v| v }.keys
required_keys = product.remark_template.keys - nullable_keys
absent_keys = required_keys - remark.keys
# remark 可添加多余字段
......
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