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 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