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 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,3 @@
1
+ module DynamicModel
2
+ VERSION = "0.0.2"
3
+ 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: []