active_dynamic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 165e1a192514f78811d5e4dc661da1dfd8971fa7
4
+ data.tar.gz: d5ed75bd800f761fe66247c9a076e7edc98b466a
5
+ SHA512:
6
+ metadata.gz: bd209021677c3b3965e2797bc03431ef5363ce845bd1ed52dad51eaf4fd10051621897c8be128196ee0676cce2c639d57810e0f5a24c7ca785898fdab231a925
7
+ data.tar.gz: cb08aec84ade3e2277a3784231978c9708419c54b386786104642cb985dd9d0c0c551a7914dce41e06c794edca11af5903ac1707334eb5346ce72218aca4307e
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.2
5
+ before_install: gem install bundler -v 1.13.2
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Constantine Lebedev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # ActiveDynamic
2
+
3
+ ActiveDynamic allows to dynamically add properties to your ActiveRecord models and
4
+ work with them as regular properties.
5
+ To see this in practice, check out the demo application available at [https://github.com/koss-lebedev/active_dynamic_demo](https://github.com/koss-lebedev/active_dynamic_demo)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'active_dynamic'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install active_dynamic
22
+
23
+ ## Usage
24
+
25
+ To make this gem work, first you need to include `HasDynamicAttributes` concern to the model that needs to have dynamic
26
+ attributses. For example, if you have `Profile` model:
27
+
28
+ ```ruby
29
+ class Profile < ActiveRecord::Base
30
+ include DynamicAttributes::HasDynamicAttributes
31
+
32
+ # ...
33
+ end
34
+ ```
35
+
36
+ After that you need to set a class that will resolve definitions of the dynamic attributes to be created on `Profile` model:
37
+
38
+ ```ruby
39
+ # lib/initializers/dynamic_attribute.rb
40
+
41
+ ActiveDynamic.configure do |config|
42
+ config.provider_class = ProfileAttributeProvider
43
+ end
44
+
45
+ class ProfileAttributeProvider
46
+
47
+ # Constructor will receive instance of a model to which dynamic attributes are added
48
+ def initialize(model)
49
+ @model = model
50
+ end
51
+
52
+ # This method has to return array of dynamic field definitions.
53
+ # You can get it from the configuration file, DB, etc., depending on your app logic
54
+ def call
55
+ [
56
+ # attribute definition has to specify attribute name, datatype, and optionally default value
57
+ ActiveDynamic::AttributeDefinition.new('age', ActiveDynamic::DataType::Integer, 18),
58
+ ActiveDynamic::AttributeDefinition.new('biography', ActiveDynamic::DataType::Text)
59
+ ]
60
+ end
61
+
62
+ end
63
+
64
+ ```
65
+
66
+
67
+ ## Contributing
68
+
69
+ Bug reports and pull requests are welcome on GitHub at https://github.com/koss-lebedev/active_dynamic. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
70
+
71
+
72
+ ## License
73
+
74
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
75
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_dynamic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'active_dynamic'
8
+ spec.version = ActiveDynamic::VERSION
9
+ spec.authors = ['Constantine Lebedev']
10
+ spec.email = ['koss.lebedev@gmail.com']
11
+
12
+ spec.summary = 'Gem that allows to attach dynamic attributes to ActiveRecord model'
13
+ spec.description = 'Gem that allows to attach dynamic attributes to ActiveRecord model'
14
+ spec.homepage = 'https://github.com/koss-lebedev/active_dynamic'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'activerecord', '>= 4.0', '< 5.1'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.13'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'minitest', '~> 5.0'
29
+ spec.add_development_dependency 'sqlite3'
30
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'active_dynamic'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,9 @@
1
+ module ActiveDynamic
2
+ class Attribute < ActiveRecord::Base
3
+ belongs_to :customizable, polymorphic: true
4
+
5
+ self.table_name = 'active_dynamic_attributes'
6
+ validates :name, presence: true
7
+ validates :datatype, presence: true, inclusion: DataType::All
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveDynamic
2
+ class AttributeDefinition < Struct.new(:name, :datatype, :value)
3
+ end
4
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveDynamic
2
+
3
+ @@configuration = nil
4
+
5
+ def self.configure
6
+ @@configuration = Configuration.new
7
+
8
+ if block_given?
9
+ yield configuration
10
+ end
11
+
12
+ configuration
13
+ end
14
+
15
+ def self.configuration
16
+ @@configuration || configure
17
+ end
18
+
19
+ class Configuration
20
+
21
+ def provider_class
22
+ @provider_class || NullProvider
23
+ end
24
+
25
+ def provider_class=(klass)
26
+ @provider_class = klass
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveDynamic
2
+ module DataType
3
+ String = 0
4
+ Text = 1
5
+ Integer = 2
6
+ Decimal = 3
7
+ Date = 4
8
+ DateTime = 5
9
+
10
+ All = [String, Text, Integer, Decimal]
11
+ end
12
+ end
@@ -0,0 +1,71 @@
1
+ module ActiveDynamic
2
+ module HasDynamicAttributes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :active_dynamic_attributes, class_name: ActiveDynamic::Attribute, autosave: true, as: :customizable
7
+
8
+ after_initialize :load_dynamic_attributes
9
+ before_save :save_dynamic_attributes
10
+ end
11
+
12
+ # def initialize(*args)
13
+ # super
14
+ # load_dynamic_attributes
15
+ # end
16
+
17
+ def dynamic_attributes
18
+ if persisted?
19
+ active_dynamic_attributes.order(:created_at)
20
+ else
21
+ ActiveDynamic.configuration.provider_class.new(self).call
22
+ end
23
+ end
24
+
25
+ def dynamic_attr_names
26
+ dynamic_attributes.map(&:name)
27
+ end
28
+
29
+ private
30
+
31
+ def generate_accessors(fields)
32
+ fields.map(&:name).each do |field|
33
+
34
+ define_singleton_method(field) do
35
+ _custom_fields[field]
36
+ end
37
+
38
+ define_singleton_method("#{field}=") do |value|
39
+ _custom_fields[field] = value.strip
40
+ end
41
+
42
+ end
43
+ end
44
+
45
+ def _custom_fields
46
+ @_custom_fields ||= ActiveSupport::HashWithIndifferentAccess.new
47
+ end
48
+
49
+ def load_dynamic_attributes
50
+ dynamic_attributes.each do |ticket_field|
51
+ _custom_fields[ticket_field.name] = ticket_field.value
52
+ end
53
+
54
+ generate_accessors dynamic_attributes
55
+ end
56
+
57
+ def save_dynamic_attributes
58
+ dynamic_attributes.each do |field|
59
+ attr = self.active_dynamic_attributes.find_or_initialize_by(name: field.name, datatype: field.datatype)
60
+ if _custom_fields[field.name]
61
+ if self.persisted?
62
+ attr.update(value: _custom_fields[field.name])
63
+ else
64
+ attr.assign_attributes(value: _custom_fields[field.name])
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,8 @@
1
+ ActiveDynamic.configure do |config|
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
6
+ config.provider_class = ActiveDynamic::NullProvider
7
+
8
+ end
@@ -0,0 +1,19 @@
1
+ class CreateActiveDynamicAttributesTable < ActiveRecord::Migration[4.2]
2
+
3
+ def change
4
+ create_table :active_dynamic_attributes do |t|
5
+ t.integer :customizable_id, null: false
6
+ t.string :customizable_type, limit: 50
7
+
8
+ t.string :name, null: false
9
+ t.text :value
10
+ t.integer :datatype, null: false
11
+
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :active_dynamic_attributes, :customizable_id
16
+ add_index :active_dynamic_attributes, :customizable_type
17
+ end
18
+
19
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveDynamic
2
+ class NullProvider
3
+
4
+ def initialize(model_class)
5
+ end
6
+
7
+ def call
8
+ []
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveDynamic
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_record'
2
+
3
+ require 'active_dynamic/data_type'
4
+ require 'active_dynamic/attribute_definition'
5
+ require 'active_dynamic/attribute'
6
+ require 'active_dynamic/has_dynamic_attributes'
7
+ require 'active_dynamic/configuration'
8
+ require 'active_dynamic/null_provider'
9
+ require 'active_dynamic/version'
@@ -0,0 +1,25 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/active_record'
3
+
4
+ class ActiveDynamicGenerator < ActiveRecord::Generators::Base
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
8
+ argument :name, type: :string, default: 'dummy'
9
+
10
+ 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
+
13
+ source_root File.expand_path('../../active_dynamic', __FILE__)
14
+
15
+ def copy_files
16
+ return if options['skip-migration']
17
+ migration_template 'migration.rb', 'db/migrate/create_active_dynamic_attributes_table.rb'
18
+ end
19
+
20
+ def create_initializer
21
+ return if options['skip-initializer']
22
+ copy_file 'initializer.rb', 'config/initializers/active_dynamic.rb'
23
+ end
24
+
25
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_dynamic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Constantine Lebedev
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-02-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.13'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.13'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: minitest
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '5.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: sqlite3
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: Gem that allows to attach dynamic attributes to ActiveRecord model
90
+ email:
91
+ - koss.lebedev@gmail.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".gitignore"
97
+ - ".travis.yml"
98
+ - Gemfile
99
+ - LICENSE.txt
100
+ - README.md
101
+ - Rakefile
102
+ - active_dynamic.gemspec
103
+ - bin/console
104
+ - bin/setup
105
+ - lib/active_dynamic.rb
106
+ - lib/active_dynamic/attribute.rb
107
+ - lib/active_dynamic/attribute_definition.rb
108
+ - lib/active_dynamic/configuration.rb
109
+ - lib/active_dynamic/data_type.rb
110
+ - lib/active_dynamic/has_dynamic_attributes.rb
111
+ - lib/active_dynamic/initializer.rb
112
+ - lib/active_dynamic/migration.rb
113
+ - lib/active_dynamic/null_provider.rb
114
+ - lib/active_dynamic/version.rb
115
+ - lib/generators/active_dynamic_generator.rb
116
+ homepage: https://github.com/koss-lebedev/active_dynamic
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.4.8
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Gem that allows to attach dynamic attributes to ActiveRecord model
140
+ test_files: []