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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d92620f81411267c43b5853c4c9ba25318127c3
4
- data.tar.gz: e3aa5c3a7543620f6926e76ed7cfcdbe29509bac
3
+ metadata.gz: 3649641f159230e047d4ed22374903e4d44c5448
4
+ data.tar.gz: f62be0e967b33900ef4ca9a02065e87309a1e57c
5
5
  SHA512:
6
- metadata.gz: 25e660b0d0963fd391a4b05da9d737f01d660f3f77314753140ec6440744352614211eb6cbb8240ab6ba658e9e653089efe10c1a2e202de00b1b52fcafaf490b
7
- data.tar.gz: e495087ab547802acda4ebfacbc432f6bae19911b42e5b3679c3f3c0a4f7a0d6eac510ac7eaa9f88c065cfd9e733ffdee72624373656cee19dffcc71515c2813
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 of a model to which dynamic attributes are added
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
@@ -7,4 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- task :default => :test
10
+ task default: :test
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'active_dynamic/version'
@@ -7,4 +7,4 @@ require 'active_dynamic/has_dynamic_attributes'
7
7
  require 'active_dynamic/active_record'
8
8
  require 'active_dynamic/configuration'
9
9
  require 'active_dynamic/null_provider'
10
- require 'active_dynamic/version'
10
+ require 'active_dynamic/version'
@@ -8,4 +8,4 @@ module ActiveDynamic
8
8
  end
9
9
  end
10
10
 
11
- ActiveRecord::Base.extend ActiveDynamic::ActiveRecord
11
+ ActiveRecord::Base.extend ActiveDynamic::ActiveRecord
@@ -1,14 +1,21 @@
1
1
  module ActiveDynamic
2
2
  class AttributeDefinition
3
3
 
4
- attr_reader :display_name, :name, :datatype, :value
4
+ attr_reader :display_name, :datatype, :value, :name
5
5
 
6
- def initialize(display_name, options = {})
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
- @name = options[:system_name] || display_name.gsub(/[^a-zA-Z\s]/, ''.freeze).gsub(/\s+/, '_'.freeze)
9
- @datatype = options[:datatype]
10
- @value = options[:default_value]
11
- @required = options[:required]
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 provider_class=(klass)
26
- @provider_class = klass
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, class_name: ActiveDynamic::Attribute, autosave: true, as: :customizable
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
- active_dynamic_attributes.order(:created_at)
15
+ if persisted? && any_dynamic_attributes?
16
+ should_resolve_persisted? ? resolve_combined : resolve_from_db
13
17
  else
14
- ActiveDynamic.configuration.provider_class.new(self.class).call
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
- load_dynamic_attributes
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
- private
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
- self.singleton_class.instance_eval do
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
- props = { name: field.name, display_name: field.display_name,
82
- datatype: field.datatype, value: field.value }
83
- attr = self.active_dynamic_attributes.find_or_initialize_by(props)
84
- if _custom_fields[field.name]
85
- if self.persisted?
86
- attr.update(value: _custom_fields[field.name])
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 inyour application responsible for resolving dynamic properties for your model.
4
- # this class should accept `model` as the only constructor parameter, and have a `call` method
5
- # that returns an array of AttributeDefinition
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.text :value
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
@@ -1,7 +1,7 @@
1
1
  module ActiveDynamic
2
2
  class NullProvider
3
3
 
4
- def initialize(model_class)
4
+ def initialize(model)
5
5
  end
6
6
 
7
7
  def call
@@ -1,3 +1,3 @@
1
1
  module ActiveDynamic
2
- VERSION = '0.5.4'
2
+ VERSION = '0.5.5'.freeze
3
3
  end
@@ -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 which requires a NAME parameter for the
7
- # new table name. Our generator always uses 'active_dynamic_attributes', so default value is irrelevant
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', :type => :boolean, :desc => "Don't generate an 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
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-04-17 00:00:00.000000000 Z
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