active_dynamic 0.5.4 → 0.5.5
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 +4 -4
- data/.rubocop.yml +17 -0
- data/README.md +66 -2
- data/Rakefile +1 -1
- data/active_dynamic.gemspec +1 -0
- data/lib/active_dynamic.rb +1 -1
- data/lib/active_dynamic/active_record.rb +1 -1
- data/lib/active_dynamic/attribute_definition.rb +14 -7
- data/lib/active_dynamic/configuration.rb +5 -7
- data/lib/active_dynamic/has_dynamic_attributes.rb +51 -20
- data/lib/active_dynamic/initializer.rb +11 -4
- data/lib/active_dynamic/migration.rb +2 -2
- data/lib/active_dynamic/null_provider.rb +1 -1
- data/lib/active_dynamic/version.rb +1 -1
- data/lib/generators/active_dynamic_generator.rb +5 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3649641f159230e047d4ed22374903e4d44c5448
|
4
|
+
data.tar.gz: f62be0e967b33900ef4ca9a02065e87309a1e57c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b66e1bdbfc527daf2ba731c19a49ebc5019e0eaf02cbc9956a2829c0dda4b78dc186defa64d4e93e6d405ed82b632a0c31d1361fac66e0eef0339825ce70384
|
7
|
+
data.tar.gz: 35bf9ad3bbbb62cd89eba2aec8b5c5afcf50cddb12ba7af69c357e5f2c9459d04c58ee08b460381126d520d9bda3c6a33170abf38555b6d8a31ad1718bbb327d
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Documentation:
|
2
|
+
Enabled: false
|
3
|
+
|
4
|
+
Style/EmptyLinesAroundBody:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Style/EmptyLinesAroundBlockBody:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Style/EmptyLinesAroundClassBody:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Style/EmptyLinesAroundMethodBody:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Style/EmptyLinesAroundModuleBody:
|
17
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/active_dynamic)
|
4
4
|
[](https://codeclimate.com/github/koss-lebedev/active_dynamic)
|
5
|
+
[](https://travis-ci.org/koss-lebedev/active_dynamic)
|
5
6
|
|
6
7
|
ActiveDynamic allows to dynamically add properties to your ActiveRecord models and
|
7
8
|
work with them as regular properties.
|
@@ -48,9 +49,9 @@ end
|
|
48
49
|
|
49
50
|
class ProfileAttributeProvider
|
50
51
|
|
51
|
-
# Constructor will receive instance
|
52
|
+
# Constructor will receive an instance to which dynamic attributes are added
|
52
53
|
def initialize(model)
|
53
|
-
@model = model
|
54
|
+
@model = model
|
54
55
|
end
|
55
56
|
|
56
57
|
# This method has to return array of dynamic field definitions.
|
@@ -70,6 +71,69 @@ end
|
|
70
71
|
|
71
72
|
```
|
72
73
|
|
74
|
+
To resolve dynamic attribute definitions for more than one model:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class Profile < ActiveRecord::Base
|
78
|
+
has_dynamic_attributes
|
79
|
+
|
80
|
+
# ...
|
81
|
+
end
|
82
|
+
|
83
|
+
class Document < ActiveRecord::Base
|
84
|
+
has_dynamic_attributes
|
85
|
+
|
86
|
+
# ...
|
87
|
+
end
|
88
|
+
|
89
|
+
class ProfileAttributeProvider
|
90
|
+
|
91
|
+
def initialize(model)
|
92
|
+
@model = model
|
93
|
+
end
|
94
|
+
|
95
|
+
def call
|
96
|
+
case @model
|
97
|
+
when Profile
|
98
|
+
[
|
99
|
+
# attribute definitions for Profile model
|
100
|
+
]
|
101
|
+
when Document
|
102
|
+
[
|
103
|
+
# attribute definitions for Document model
|
104
|
+
]
|
105
|
+
else
|
106
|
+
[]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
## How ActiveDynamic resolves dynamic attributes
|
114
|
+
|
115
|
+
When you work with unsaved models, ActiveDynamic will use `provider_class` to resolve a list
|
116
|
+
of dynamic attributes, and it will store them alongside the model when the model is saved.
|
117
|
+
So next time when you load that model from DB, ActiveDynamic won't look into `provider_class`
|
118
|
+
and it will load only the dynamic attributes that were created when the model was saved for
|
119
|
+
the first time.
|
120
|
+
|
121
|
+
If you want dynamic attributes to be resolved from `provider_class` for persisted models as well,
|
122
|
+
you can use `resolve_persisted` configuration option:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
# lib/initializers/dynamic_attribute.rb
|
126
|
+
|
127
|
+
ActiveDynamic.configure do |config|
|
128
|
+
# ...
|
129
|
+
|
130
|
+
# you can set it to Bool value to apply the behavior to all models
|
131
|
+
config.resolve_persisted = true
|
132
|
+
|
133
|
+
# or you can set it to a Proc to configure the behavior on per-class basis
|
134
|
+
config.resolve_persisted = Proc.new { |model| model.is_a?(Profile) ? true : false }
|
135
|
+
end
|
136
|
+
```
|
73
137
|
|
74
138
|
## Contributing
|
75
139
|
|
data/Rakefile
CHANGED
data/active_dynamic.gemspec
CHANGED
data/lib/active_dynamic.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
module ActiveDynamic
|
2
2
|
class AttributeDefinition
|
3
3
|
|
4
|
-
attr_reader :display_name, :
|
4
|
+
attr_reader :display_name, :datatype, :value, :name
|
5
5
|
|
6
|
-
def initialize(display_name,
|
6
|
+
def initialize(display_name, params = {})
|
7
|
+
options = params.dup
|
8
|
+
@name = (options.delete(:system_name) || display_name).parameterize.underscore
|
7
9
|
@display_name = display_name
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
|
10
|
+
@datatype = options.delete(:datatype)
|
11
|
+
@value = options.delete(:default_value)
|
12
|
+
@required = options.delete(:required)
|
13
|
+
|
14
|
+
# custom attributes from Provider
|
15
|
+
options.each do |key, value|
|
16
|
+
self.instance_variable_set("@#{key}", value)
|
17
|
+
self.class.send(:attr_reader, key)
|
18
|
+
end
|
12
19
|
end
|
13
20
|
|
14
21
|
def required?
|
@@ -16,4 +23,4 @@ module ActiveDynamic
|
|
16
23
|
end
|
17
24
|
|
18
25
|
end
|
19
|
-
end
|
26
|
+
end
|
@@ -4,11 +4,7 @@ module ActiveDynamic
|
|
4
4
|
|
5
5
|
def self.configure
|
6
6
|
@@configuration = Configuration.new
|
7
|
-
|
8
|
-
if block_given?
|
9
|
-
yield configuration
|
10
|
-
end
|
11
|
-
|
7
|
+
yield configuration if block_given?
|
12
8
|
configuration
|
13
9
|
end
|
14
10
|
|
@@ -22,9 +18,11 @@ module ActiveDynamic
|
|
22
18
|
@provider_class || NullProvider
|
23
19
|
end
|
24
20
|
|
25
|
-
def
|
26
|
-
@
|
21
|
+
def resolve_persisted
|
22
|
+
@resolve_persisted || false
|
27
23
|
end
|
28
24
|
|
25
|
+
attr_writer :provider_class, :resolve_persisted
|
26
|
+
|
29
27
|
end
|
30
28
|
end
|
@@ -3,15 +3,19 @@ module ActiveDynamic
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
-
has_many :active_dynamic_attributes,
|
6
|
+
has_many :active_dynamic_attributes,
|
7
|
+
class_name: ActiveDynamic::Attribute,
|
8
|
+
autosave: true,
|
9
|
+
dependent: :destroy,
|
10
|
+
as: :customizable
|
7
11
|
before_save :save_dynamic_attributes
|
8
12
|
end
|
9
13
|
|
10
14
|
def dynamic_attributes
|
11
|
-
if persisted?
|
12
|
-
|
15
|
+
if persisted? && any_dynamic_attributes?
|
16
|
+
should_resolve_persisted? ? resolve_combined : resolve_from_db
|
13
17
|
else
|
14
|
-
|
18
|
+
resolve_from_provider
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
@@ -23,10 +27,8 @@ module ActiveDynamic
|
|
23
27
|
if super
|
24
28
|
true
|
25
29
|
else
|
26
|
-
unless dynamic_attributes_loaded?
|
27
|
-
|
28
|
-
end
|
29
|
-
dynamic_attributes.find { |attr| attr.name == method_name.to_s.gsub(/=/, '') }.present?
|
30
|
+
load_dynamic_attributes unless dynamic_attributes_loaded?
|
31
|
+
dynamic_attributes.find { |attr| attr.name == method_name.to_s.delete('=') }.present?
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
@@ -39,7 +41,39 @@ module ActiveDynamic
|
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
|
44
|
+
private
|
45
|
+
|
46
|
+
def should_resolve_persisted?
|
47
|
+
value = ActiveDynamic.configuration.resolve_persisted
|
48
|
+
case value
|
49
|
+
when TrueClass, FalseClass
|
50
|
+
value
|
51
|
+
when Proc
|
52
|
+
value.call(self)
|
53
|
+
else
|
54
|
+
raise "Invalid configuration for resolve_persisted. Value should be Bool or Proc, got #{value.class}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def any_dynamic_attributes?
|
59
|
+
active_dynamic_attributes.any?
|
60
|
+
end
|
61
|
+
|
62
|
+
def resolve_combined
|
63
|
+
attributes = resolve_from_db
|
64
|
+
resolve_from_provider.each do |attribute|
|
65
|
+
attributes << ActiveDynamic::Attribute.new(attribute.as_json) unless attributes.find { |attr| attr.name == attribute.name }
|
66
|
+
end
|
67
|
+
attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
def resolve_from_db
|
71
|
+
active_dynamic_attributes
|
72
|
+
end
|
73
|
+
|
74
|
+
def resolve_from_provider
|
75
|
+
ActiveDynamic.configuration.provider_class.new(self).call
|
76
|
+
end
|
43
77
|
|
44
78
|
def generate_accessors(fields)
|
45
79
|
fields.each do |field|
|
@@ -58,7 +92,7 @@ module ActiveDynamic
|
|
58
92
|
end
|
59
93
|
|
60
94
|
def add_presence_validator(attribute)
|
61
|
-
|
95
|
+
singleton_class.instance_eval do
|
62
96
|
validates_presence_of(attribute)
|
63
97
|
end
|
64
98
|
end
|
@@ -78,18 +112,15 @@ module ActiveDynamic
|
|
78
112
|
|
79
113
|
def save_dynamic_attributes
|
80
114
|
dynamic_attributes.each do |field|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
else
|
88
|
-
attr.assign_attributes(value: _custom_fields[field.name])
|
89
|
-
end
|
115
|
+
next unless _custom_fields[field.name]
|
116
|
+
attr = active_dynamic_attributes.find_or_initialize_by(field.as_json)
|
117
|
+
if persisted?
|
118
|
+
attr.update(value: _custom_fields[field.name])
|
119
|
+
else
|
120
|
+
attr.assign_attributes(value: _custom_fields[field.name])
|
90
121
|
end
|
91
122
|
end
|
92
123
|
end
|
93
124
|
|
94
125
|
end
|
95
|
-
end
|
126
|
+
end
|
@@ -1,8 +1,15 @@
|
|
1
1
|
ActiveDynamic.configure do |config|
|
2
2
|
|
3
|
-
# Specify class
|
4
|
-
#
|
5
|
-
#
|
3
|
+
# Specify class in your application responsible for resolving dynamic
|
4
|
+
# properties for your model. This class should accept `model` as the
|
5
|
+
# only constructor parameter, and have a `call` method that returns
|
6
|
+
# an array of AttributeDefinition
|
6
7
|
config.provider_class = ActiveDynamic::NullProvider
|
8
|
+
|
9
|
+
# When new dynamic attributes are defined after object was saved,
|
10
|
+
# should object get this attributes automatically when editing?
|
11
|
+
# New attribute definitions are created automatically.
|
12
|
+
# Set true or false (default)
|
13
|
+
# config.resolve_persisted = true
|
7
14
|
|
8
|
-
end
|
15
|
+
end
|
@@ -5,10 +5,10 @@ class CreateActiveDynamicAttributesTable < ActiveRecord::Migration[4.2]
|
|
5
5
|
t.integer :customizable_id, null: false
|
6
6
|
t.string :customizable_type, limit: 50
|
7
7
|
|
8
|
-
t.string :display_name, null: false
|
9
8
|
t.string :name
|
10
|
-
t.
|
9
|
+
t.string :display_name, null: false
|
11
10
|
t.integer :datatype
|
11
|
+
t.text :value
|
12
12
|
t.boolean :required, null: false, default: false
|
13
13
|
|
14
14
|
t.timestamps
|
@@ -3,12 +3,13 @@ require 'rails/generators/active_record'
|
|
3
3
|
|
4
4
|
class ActiveDynamicGenerator < ActiveRecord::Generators::Base
|
5
5
|
|
6
|
-
# ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase
|
7
|
-
#
|
6
|
+
# ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase
|
7
|
+
# which requires a NAME parameter for the new table name. Our generator
|
8
|
+
# always uses 'active_dynamic_attributes', so default value is irrelevant
|
8
9
|
argument :name, type: :string, default: 'dummy'
|
9
10
|
|
10
11
|
class_option :'skip-migration', type: :boolean, desc: "Don't generate a migration for the dynamic attributes table"
|
11
|
-
class_option :'skip-initializer', :
|
12
|
+
class_option :'skip-initializer', type: :boolean, desc: "Don't generate an initializer"
|
12
13
|
|
13
14
|
source_root File.expand_path('../../active_dynamic', __FILE__)
|
14
15
|
|
@@ -22,4 +23,4 @@ class ActiveDynamicGenerator < ActiveRecord::Generators::Base
|
|
22
23
|
copy_file 'initializer.rb', 'config/initializers/active_dynamic.rb'
|
23
24
|
end
|
24
25
|
|
25
|
-
end
|
26
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_dynamic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Constantine Lebedev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -94,6 +94,7 @@ extensions: []
|
|
94
94
|
extra_rdoc_files: []
|
95
95
|
files:
|
96
96
|
- ".gitignore"
|
97
|
+
- ".rubocop.yml"
|
97
98
|
- ".travis.yml"
|
98
99
|
- Gemfile
|
99
100
|
- LICENSE.txt
|