active_dynamic 0.5.4 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/active_dynamic.svg)](https://badge.fury.io/rb/active_dynamic)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/koss-lebedev/active_dynamic/badges/gpa.svg)](https://codeclimate.com/github/koss-lebedev/active_dynamic)
|
5
|
+
[![Build Status](https://travis-ci.org/koss-lebedev/active_dynamic.svg?branch=master)](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
|