dynamic_model 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/lib/dynamic_model/entity_field.rb +27 -0
- data/lib/dynamic_model/entity_field_value.rb +12 -0
- data/lib/dynamic_model/field_manager.rb +33 -0
- data/lib/dynamic_model/field_type.rb +33 -0
- data/lib/dynamic_model/model.rb +16 -0
- data/lib/dynamic_model/properties_data.rb +167 -0
- data/lib/dynamic_model/version.rb +3 -0
- data/lib/dynamic_model.rb +23 -0
- metadata +63 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b0fff99bdc0acf548ab6316034463e47b152e79fd164ee68a7aaaa4d0ec4e763
|
|
4
|
+
data.tar.gz: '08fd1f2531f70761a84ae37009511d18e7bb3e2ad49c4722cca71162ac292253'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c9b5bc9f0c7fbcd724262aac9dc1139cab60b3d1a8342347fc7a03cc984fcaf3e5ce6952b0add94b96a25315553482f0bcbfd3a3f66a4b4f5281e8b0e37d6cf8
|
|
7
|
+
data.tar.gz: 4bf5054fdb29a66bd6e6159da7875f2742a4776e2dd8a9c4eadcc7d2d52cb4131ae4d5b390039fb0ee7e2fb4afae6ca14d1d0840bd216940676c0aeac1b54533
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2020 Max Ivak
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module DynamicModel
|
|
2
|
+
module EntityField
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
|
|
7
|
+
def self.get_by_name(name)
|
|
8
|
+
row = where(name: name).first
|
|
9
|
+
row
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.get_id_by_name(name)
|
|
13
|
+
row = get_by_name name
|
|
14
|
+
return nil if row.nil?
|
|
15
|
+
|
|
16
|
+
row.id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module DynamicModel
|
|
2
|
+
module EntityFieldValue
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
belongs_to :entity, class_name: dynamic_model_settings[:entity_class].name, foreign_key: :entity_id
|
|
7
|
+
belongs_to :field, class_name: dynamic_model_settings[:field_class].name, foreign_key: :field_id
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module DynamicModel
|
|
2
|
+
class FieldManager
|
|
3
|
+
attr_accessor :options
|
|
4
|
+
|
|
5
|
+
def initialize(options={})
|
|
6
|
+
@options = options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def field_class
|
|
10
|
+
@field_class ||= @options[:field_class]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_field(name, type, opts={})
|
|
14
|
+
is_multivalue = opts[:multivalue] || false
|
|
15
|
+
is_versioned = opts[:versioned] || false
|
|
16
|
+
field = field_class.new(
|
|
17
|
+
name: name,
|
|
18
|
+
type_id: FieldType.get_id_by_name(type),
|
|
19
|
+
multivalue: is_multivalue,
|
|
20
|
+
versioned: is_versioned,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
res = field.save
|
|
24
|
+
|
|
25
|
+
res
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_field_by_name(field_name)
|
|
29
|
+
field_class.where(name: field_name).first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module DynamicModel
|
|
2
|
+
class FieldType
|
|
3
|
+
INT = 1
|
|
4
|
+
STRING = 2
|
|
5
|
+
DATETIME = 3
|
|
6
|
+
TEXT = 4
|
|
7
|
+
ENUM = 5
|
|
8
|
+
DATE = 6
|
|
9
|
+
TIME = 7
|
|
10
|
+
BOOL = 8
|
|
11
|
+
IMAGE = 9
|
|
12
|
+
REFERENCE = 10
|
|
13
|
+
MONEY = 11
|
|
14
|
+
|
|
15
|
+
TYPES = {
|
|
16
|
+
:int => INT,
|
|
17
|
+
:string => STRING,
|
|
18
|
+
:text => TEXT,
|
|
19
|
+
:bool => BOOL,
|
|
20
|
+
:reference => REFERENCE,
|
|
21
|
+
:money => MONEY,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def self.get_id_by_name(name)
|
|
25
|
+
TYPES[name.to_sym]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.get_name_by_id(id)
|
|
29
|
+
TYPES.invert[id].to_s
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module DynamicModel
|
|
2
|
+
module Model
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
def properties
|
|
7
|
+
@properties ||= DynamicModel::PropertiesData.new(self, self.class.dynamic_model_settings)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.properties
|
|
11
|
+
FieldManager.new(dynamic_model_settings)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module DynamicModel
|
|
2
|
+
class PropertiesData
|
|
3
|
+
attr_accessor :options
|
|
4
|
+
|
|
5
|
+
# cache
|
|
6
|
+
@properties_cache = {}
|
|
7
|
+
|
|
8
|
+
def initialize(entity_record, options={})
|
|
9
|
+
@entity_record = entity_record
|
|
10
|
+
@options = options
|
|
11
|
+
|
|
12
|
+
@properties_cache = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def method_missing(method_name, *args, &block)
|
|
16
|
+
get(method_name.to_s)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def properties_cache
|
|
20
|
+
return @properties_cache unless @properties_cache.nil?
|
|
21
|
+
|
|
22
|
+
@properties_cache = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def entity_record
|
|
26
|
+
@entity_record
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def field_class
|
|
30
|
+
@field_class ||= @options[:field_class]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def field_value_class
|
|
34
|
+
@field_value_class ||= @options[:field_value_class]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get(field_name, def_value=nil, opts={})
|
|
38
|
+
version = opts[:version] || nil
|
|
39
|
+
|
|
40
|
+
# cache
|
|
41
|
+
v_cache, cache_exist = cache_get_value field_name, version
|
|
42
|
+
return v_cache if cache_exist
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
if field_name.is_a?(String)
|
|
46
|
+
field = get_field_by_name field_name
|
|
47
|
+
else
|
|
48
|
+
field = field_name
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return nil if field.nil?
|
|
52
|
+
|
|
53
|
+
if field.multivalue
|
|
54
|
+
value_records = get_value_records field.id, version
|
|
55
|
+
v = value_records.map{|r| get_value(r)}
|
|
56
|
+
else
|
|
57
|
+
value_record = get_value_record field.id, version
|
|
58
|
+
return nil if value_record.nil?
|
|
59
|
+
|
|
60
|
+
v = get_value value_record
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
cache_set_value field_name, version, v
|
|
64
|
+
|
|
65
|
+
v
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_value_record(field_id, version=nil)
|
|
69
|
+
field_value_class.where(entity_id: entity_record.id, field_id: field_id, version: version).first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get_value_records(field_id, version=nil)
|
|
73
|
+
field_value_class.where(entity_id: entity_record.id, field_id: field_id, version: version).all.to_a
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def set(field_name, value, opts={})
|
|
77
|
+
field = get_field_by_name field_name
|
|
78
|
+
raise 'Field not found' if field.nil?
|
|
79
|
+
|
|
80
|
+
if field.multivalue
|
|
81
|
+
set_multivalue_field_value field, value, opts
|
|
82
|
+
else
|
|
83
|
+
set_field_value field, value, opts
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def get_value(value_record)
|
|
88
|
+
type_id = value_record.field.type_id
|
|
89
|
+
if [FieldType::INT, FieldType::REFERENCE].include? type_id
|
|
90
|
+
return value_record.value.to_i
|
|
91
|
+
elsif [FieldType::TEXT].include? type_id
|
|
92
|
+
return value_record.value_text
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
value_record.value
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def set_field_value(field, value, opts={})
|
|
101
|
+
version = opts[:version] || nil
|
|
102
|
+
value_record = get_value_record field.id, version
|
|
103
|
+
|
|
104
|
+
if value_record.nil?
|
|
105
|
+
value_record = field_value_class.new(entity_id: entity_record.id, field_id: field.id, version: version)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# set value
|
|
109
|
+
if [FieldType::TEXT].include? field.type_id
|
|
110
|
+
value_record.value_text = value
|
|
111
|
+
else
|
|
112
|
+
value_record.value = value
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
value_record.save
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def set_multivalue_field_value(field, values, opts={})
|
|
119
|
+
version = opts[:version] || nil
|
|
120
|
+
|
|
121
|
+
values = values.map{|r| r.to_s}.reject(&:blank?)
|
|
122
|
+
|
|
123
|
+
# existing values
|
|
124
|
+
existing_rows = field_value_class.where(entity_id: entity_record.id, field_id: field.id, version: version).all.to_a
|
|
125
|
+
existing_values = existing_rows.map{|r| r.value}
|
|
126
|
+
|
|
127
|
+
delete_values = existing_values - values
|
|
128
|
+
new_values = values - existing_values
|
|
129
|
+
|
|
130
|
+
ActiveRecord::Base.transaction do
|
|
131
|
+
# delete
|
|
132
|
+
if delete_values.length > 0
|
|
133
|
+
field_value_class.where(entity_id: entity_record.id, field_id: field.id, value: delete_values).delete_all
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# insert
|
|
137
|
+
new_values.each do |new_value|
|
|
138
|
+
next if new_value.blank?
|
|
139
|
+
|
|
140
|
+
new_record = field_value_class.new(entity_id: entity_record.id, field_id: field.id, value: new_value)
|
|
141
|
+
new_record.save!
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def get_field_by_name(field_name)
|
|
147
|
+
field_class.where(name: field_name).first
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def cache_get_value(field_name, version)
|
|
151
|
+
version_name = version || 'default'
|
|
152
|
+
|
|
153
|
+
exist = @properties_cache.has_key?(field_name) && @properties_cache[field_name].has_key?(version_name)
|
|
154
|
+
return [nil, exist] if !exist
|
|
155
|
+
|
|
156
|
+
v = @properties_cache.dig(field_name, version_name)
|
|
157
|
+
[v, true]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def cache_set_value(field_name, version, v)
|
|
161
|
+
version_name = version || 'default'
|
|
162
|
+
@properties_cache[field_name] = {} unless @properties_cache.has_key?(field_name)
|
|
163
|
+
@properties_cache[field_name][version_name] = v
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require_relative "dynamic_model/field_type"
|
|
2
|
+
require_relative "dynamic_model/entity_field"
|
|
3
|
+
require_relative "dynamic_model/entity_field_value"
|
|
4
|
+
require_relative "dynamic_model/field_manager"
|
|
5
|
+
require_relative "dynamic_model/properties_data"
|
|
6
|
+
require_relative "dynamic_model/model"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# require "zeitwerk"
|
|
10
|
+
# loader = Zeitwerk::Loader.for_gem
|
|
11
|
+
#
|
|
12
|
+
#
|
|
13
|
+
# # loader.push_dir File.expand_path("../../../lib", __FILE__)
|
|
14
|
+
# loader.push_dir File.expand_path("../api/", __FILE__)
|
|
15
|
+
#
|
|
16
|
+
# loader.ignore("#{__dir__}/api/engine")
|
|
17
|
+
#
|
|
18
|
+
# loader.setup
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
module DynamicModel
|
|
22
|
+
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dynamic_model
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Max Ivak
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Useful for managing a large number of properties.
|
|
27
|
+
email:
|
|
28
|
+
- maxivak@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- MIT-LICENSE
|
|
34
|
+
- lib/dynamic_model.rb
|
|
35
|
+
- lib/dynamic_model/entity_field.rb
|
|
36
|
+
- lib/dynamic_model/entity_field_value.rb
|
|
37
|
+
- lib/dynamic_model/field_manager.rb
|
|
38
|
+
- lib/dynamic_model/field_type.rb
|
|
39
|
+
- lib/dynamic_model/model.rb
|
|
40
|
+
- lib/dynamic_model/properties_data.rb
|
|
41
|
+
- lib/dynamic_model/version.rb
|
|
42
|
+
homepage: https://gitlab.com/maxivak/dynamic_model
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata: {}
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubygems_version: 3.7.1
|
|
61
|
+
specification_version: 4
|
|
62
|
+
summary: Add dynamic attributes to your model
|
|
63
|
+
test_files: []
|