app/models/setup/build_in_data_type.rb
require 'edi/formater'
require 'xsd/core_ext'
module Setup
class BuildInDataType
include SchemaHandler
include DataTypeParser
attr_reader :model
def request_db_data_type
RequestStore.store["[cenit]#{self}.db_data_type".to_sym] ||= db_data_type
end
def db_data_type(create = false)
namespace = model.to_s.split('::')
name = namespace.pop
namespace = namespace.join('::')
Setup::CenitDataType.where(namespace: namespace, name: name).first ||
(create && begin
Setup::CenitDataType.create!(namespace: namespace, name: name, origin: default_origin)
rescue
puts "Error persisting build-in data for #{model}"
raise $!
end)
end
def namespace
Setup.to_s
end
def title
@title ||= model.to_s.split('::').last.to_title
end
def custom_title(separator = '|')
model.to_s.split('::').collect(&:to_title).join(" #{separator} ")
end
def name
@name ||= model.model_name.to_s
end
def data_type_name
model.to_s
end
def additional_properties?
false
end
def records_methods
[]
end
def data_type_methods
[]
end
def before_save_callbacks
[]
end
def after_save_callbacks
[]
end
def count
model.count
end
def initialize(model)
@model = model
end
def subtype?
model.superclass.include?(Mongoid::Document)
end
def records_model
model
end
def slug
model.to_s.split('::').last.underscore
end
def id
model.to_s
end
def all_data_type_storage_collections_names
if model < CrossOrigin::CenitDocument
origins = model.origins.select { |origin| Setup::Crossing.authorized_crossing_origins.include?(origin) }
origins.collect do |origin|
if origin == :default
model.collection_name
else
CrossOrigin[origin].collection_name_for(model)
end
end
else
[model.collection_name]
end
end
def rebuild_schema
@schema = build_schema
end
def schema
@schema ||= build_schema
end
def origin_config
@origin
end
def default_origin
origin_config || CenitDataType.default_origin
end
def on_origin(*args)
if args.length != 0
@origin = args[0].to_sym
end
self
end
def find_data_type(ref, ns = namespace)
BuildInDataType.build_ins[ref] ||
Setup::DataType.find_data_type(ref, ns)
end
def protecting?(field)
(@protecting || []).include?(field.to_s)
end
def protecting(*fields)
store_fields(:@protecting, *fields)
end
def embedding(*fields)
store_fields(:@embedding, *fields)
end
def exclusive_referencing(*fields)
store_fields(:@exclusive_referencing, *fields)
end
def referenced_by(*fields)
unless fields.nil?
fields = [fields] unless fields.is_a?(Enumerable)
fields << :_id
end
store_fields(:@referenced_by, *fields)
end
def and(to_merge)
if to_merge
@to_merge = (@to_merge || {}).array_hash_merge(to_merge.deep_stringify_keys)
end
self
end
def including_polymorphic(*fields)
@polymorphic_fields = (@polymorphic_fields || []) + fields.map(&:to_s)
self
end
def polymorphic_fields
fields = @polymorphic_fields || []
if (parent_build_in = BuildInDataType[model.superclass])
fields = fields + parent_build_in.polymorphic_fields
end
fields
end
def and_polymorphic(polymorphic_merge)
if polymorphic_merge
@polymorphic_merge = (@polymorphic_merge || {}).array_hash_merge(polymorphic_merge.deep_stringify_keys)
end
self
end
def polymorphic_to_merge
to_merge = @polymorphic_merge || {}
if (parent_build_in = BuildInDataType[model.superclass])
to_merge = to_merge.array_hash_merge(parent_build_in.polymorphic_to_merge)
end
to_merge
end
def with(*fields)
store_fields(:@with, *fields)
end
def including(*fields)
store_fields(:@including, fields, @including)
end
def discarding(*fields)
store_fields(:@discarding, *fields, @discarding)
end
def excluding(*fields)
store_fields(:@excluding, *fields, @excluding)
end
def bulkable_deletable(deletable)
@bulkable_deletable = deletable
end
def bulkable_deletable?
@bulkable_deletable != :denied
end
class << self
def [](ref)
build_ins[ref.to_s]
end
def build_ins
@build_ins ||= {}
end
def each(&block)
build_ins.values.each(&block)
end
def regist(model, &block)
build_ins[model.to_s] ||=
begin
model.include(Setup::OrmModelAware)
model.include(Setup::SchemaModelAware)
model.include(Edi::Formatter)
model.include(Edi::Filler)
model.include(EventLookup)
model.class.include(Mongoid::CenitExtension)
build_in = BuildInDataType.new(model)
block.call(build_in) if block
build_in
end
end
end
def ns_slug
Setup.to_s.underscore
end
EXCLUDED_FIELDS = %w(_id created_at updated_at version)
EXCLUDED_RELATIONS = %w(account creator updater tenant)
def respond_to?(*args)
args[0].to_s.start_with?('get_') || super
end
def method_missing(symbol, *args)
if symbol.to_s.start_with?('get_')
instance_variable_get(:"@#{symbol.to_s.from(4)}")
else
super
end
end
def json_schema_type(mongoid_type)
SCHEMA_TYPE_MAP[mongoid_type].dup
end
private
def store_fields(instance_variable, *fields)
if fields
fail 'Illegal argument' unless fields.present?
fields = [fields] unless fields.is_a?(Enumerable)
instance_variable_set(instance_variable, fields.flatten.collect(&:to_s).uniq.select(&:present?))
else
instance_variable_set(instance_variable, nil)
end
self
end
SCHEMA_TYPE_MAP =
{
BSON::ObjectId => { 'type' => 'string' },
Hash => { 'type' => 'object' },
Array => { 'type' => 'array' },
Integer => { 'type' => 'integer' },
BigDecimal => { 'type' => 'integer' },
Float => { 'type' => 'number' },
Numeric => { 'type' => 'number' },
Mongoid::Boolean => { 'type' => 'boolean' },
TrueClass => { 'type' => 'boolean' },
FalseClass => { 'type' => 'boolean' },
Time => { 'type' => 'string', 'format' => 'time' },
DateTime => { 'type' => 'string', 'format' => 'date-time' },
Date => { 'type' => 'string', 'format' => 'date' },
String => { 'type' => 'string' },
Symbol => { 'type' => 'string' },
Mongoid::StringifiedSymbol => { 'type' => 'string', 'format' => 'symbol' },
nil => {},
Object => {},
Module => { 'type' => 'string' },
Class => { 'type' => 'string' }
}.freeze
def excluded?(name)
name = name.to_s
(@excluding && @excluding.include?(name)) || EXCLUDED_FIELDS.include?(name) || EXCLUDED_RELATIONS.include?(name)
end
def included?(name)
[:@with, :@including, :@embedding, :@discarding].any? do |v|
(v = instance_variable_get(v)) && v.include?(name)
end || polymorphic_fields.include?(name) || !(@with || excluded?(name))
end
def build_schema
@discarding ||= []
schema = Mongoff::Model.base_schema.deep_merge('properties' => { 'id' => {} })
properties = schema['properties']
if model < ClassHierarchyAware
if model.abstract?
schema['abstract'] = true
schema['descendants'] = (model.class_hierarchy - [model]).map do |sub_model|
data_type = sub_model.data_type
{
id: data_type.id.to_s,
namespace: data_type.namespace,
name: data_type.name,
abstract: sub_model.abstract?
}.stringify_keys
end
end
else
properties.delete('_type')
end
schema[:referenced_by.to_s] = Cenit::Utility.stringfy(@referenced_by) if @referenced_by
model.fields.each do |field_name, field|
next unless !field.is_a?(Mongoid::Fields::ForeignKey) && included?(field_name.to_s)
json_type = (properties[field_name] = json_schema_type(field.type))['type']
if @discarding.include?(field_name)
(properties[field_name]['edi'] ||= {})['discard'] = true
end
next unless json_type.nil? || json_type == 'object' || json_type == 'array'
unless (mongoff_models = model.instance_variable_get(:@mongoff_models))
model.instance_variable_set(:@mongoff_models, mongoff_models = {})
end
mongoff_models[field_name] = Mongoff::Model.for(
data_type: self,
name: field_name.camelize,
parent: model,
schema: properties[field_name],
cache: false,
modelable: false,
root_schema: schema)
end
model.reflect_on_all_associations(:embeds_one,
:embeds_many,
:has_one,
:belongs_to,
:has_many,
:has_and_belongs_to_many).each do |relation|
next unless included?((relation_name = relation.name.to_s))
property_schema =
case relation
when Mongoid::Association::Embedded::EmbedsOne
{
'$ref': build_ref(relation.klass)
}
when Mongoid::Association::Embedded::EmbedsMany
{
type: 'array',
items: {
'$ref': build_ref(relation.klass)
}
}
when Mongoid::Association::Referenced::HasOne
{
'$ref': build_ref(relation.klass),
referenced: true,
exclusive: (@exclusive_referencing && @exclusive_referencing.include?(relation_name)).to_b,
export_embedded: (@embedding && @embedding.include?(relation_name)).to_b
}
when Mongoid::Association::Referenced::BelongsTo
if (@including && @including.include?(relation_name.to_s)) || relation.inverse_of.nil?
{
'$ref': build_ref(relation.klass),
referenced: true,
exclusive: (@exclusive_referencing && @exclusive_referencing.include?(relation_name)).to_b,
export_embedded: (@embedding && @embedding.include?(relation_name)).to_b
}
end
when Mongoid::Association::Referenced::HasMany, Mongoid::Association::Referenced::HasAndBelongsToMany
{
type: 'array',
items: {
'$ref': build_ref(relation.klass)
},
referenced: true,
exclusive: (@exclusive_referencing && @exclusive_referencing.include?(relation_name)).to_b,
export_embedded: (@embedding && @embedding.include?(relation_name)).to_b
}
end
next unless property_schema
property_schema.deep_stringify_keys!
if @discarding.include?(relation_name.to_s)
(property_schema['edi'] ||= {})['discard'] = true
end
properties[relation_name] = property_schema
end
schema['protected'] = @protecting if @protecting.present?
schema = schema.deep_reverse_merge(polymorphic_to_merge)
schema = schema.deep_reverse_merge(@to_merge) if @to_merge
schema
end
def build_ref(klass)
tokens = klass.to_s.split('::')
{ 'name' => tokens.pop, 'namespace' => tokens.join('::') }
end
end
end