distributable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +66 -0
- data/Rakefile +37 -0
- data/app/assets/config/distributable_manifest.js +2 -0
- data/app/assets/javascripts/distributable/application.js +13 -0
- data/app/assets/stylesheets/distributable/application.css +15 -0
- data/app/controllers/distributable/application_controller.rb +5 -0
- data/app/helpers/distributable/application_helper.rb +4 -0
- data/app/jobs/distributable/application_job.rb +4 -0
- data/app/mailers/distributable/application_mailer.rb +6 -0
- data/app/models/distributable/application_record.rb +5 -0
- data/app/views/layouts/distributable/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/distributable.rb +105 -0
- data/lib/distributable/engine.rb +11 -0
- data/lib/distributable/version.rb +3 -0
- data/lib/tasks/distributable_tasks.rake +28 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 12c155b9647754f6a436ba5ce5a74c28b4062513
|
4
|
+
data.tar.gz: e1c8881c0786a9e363b5e9056a607e378613def5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 33fbf2d244ed7ff582880a430778c08f405ed92e07bde809e849193a279281aeaea09f1bc0d798b7d1f1cb2fd3533be6797749f11718f959630131c49021d408
|
7
|
+
data.tar.gz: 4335887220ce9d2f5fb576dc6f7867642f9cd308eda0866e646eb6a869fb88169d1c0ffa03e42fe40ded9ac6379b874d1f79cdab1ef65185ccd42ee2343ed9fd
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Chris Liaw
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# What is Distributable?
|
2
|
+
|
3
|
+
Distributable gem is to assist in creating the framework for the data to be distributed across multiple nodes.
|
4
|
+
|
5
|
+
What it does at this version is to:
|
6
|
+
* Add 'identifier' field to the table structure. This field shall be populated with SecureRandom UUID during record creation and duplication is checked before commit. If you have already has the field defined then the field shall be used.
|
7
|
+
* Add change_logs table to keep track of which distributable table has changed (added/edited/deleted). These changed record is the target to be synced across to other database later.
|
8
|
+
|
9
|
+
### Motivation
|
10
|
+
This comes when a distributed issue tracking system is being formulated to match the benefit of distributed issue tracker such as Git. Git has numerous value to developer in mobile, such as myself that could allow the source code to be distributed even without a central server. If the Git does not strictly need a central server, we are opted that issue tracker, which tied to the source of the code changed that is translated into the Git commit, should also be distributed to ensure the pace of the issue resolution is in tandem with the Git check ins.
|
11
|
+
|
12
|
+
### Why not just used database that has distributed feature?
|
13
|
+
There are some RDBMS or NoSQL database do already have high grade distribution feature included such as PostgreSQL, Hadoop, CouchBase etc. However not all database has it. This mainly is to provide consistant distributable feature to the system that is being build without depending on database selection.
|
14
|
+
|
15
|
+
Also using this approach allow more fine grind control on data such that in a database, not all data would be suitable to be synced. One example would be local use access table. Local use access is meant to control which user has what access when the user is connecting to the system. That has no meaning or even harmful to other node as the access rights of a particular user in my node (Imagine you can push your changes to my laptop and your access rights does not mean the same with other team member who is a junior developer that consistantly breaks things).
|
16
|
+
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
This gem is designed and tested under Rails environment.
|
20
|
+
|
21
|
+
For a particular table that you wish to add to the distributable scope (here the sample table is DistributedRecord):
|
22
|
+
```ruby
|
23
|
+
class DistributedRecord < AppicationRecord
|
24
|
+
distributable
|
25
|
+
end
|
26
|
+
```
|
27
|
+
and you are done!
|
28
|
+
|
29
|
+
Upon record creation, a random SecureRandom UUID shall be inserted into the field identifier and changes would be recorded into the change_logs table created via command
|
30
|
+
```bash
|
31
|
+
$ rake distributable:setup
|
32
|
+
```
|
33
|
+
|
34
|
+
You can check if a model is marked as distributable by checking
|
35
|
+
```ruby
|
36
|
+
@model.distributed?
|
37
|
+
```
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
Add this line to your application's Gemfile:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
gem 'distributable'
|
44
|
+
```
|
45
|
+
|
46
|
+
And then execute:
|
47
|
+
```bash
|
48
|
+
$ bundle
|
49
|
+
$ rake distributable:setup
|
50
|
+
```
|
51
|
+
The rake command only required to execute once per project.
|
52
|
+
|
53
|
+
Or install it yourself as:
|
54
|
+
```bash
|
55
|
+
$ gem install distributable
|
56
|
+
```
|
57
|
+
If you install yourself, after included into the Gemfile, you still required to execute
|
58
|
+
```bash
|
59
|
+
$ rake distributable:setup
|
60
|
+
```
|
61
|
+
|
62
|
+
## Contributing
|
63
|
+
You can contribute by forking the project. Just drop me a message so that I know who's
|
64
|
+
|
65
|
+
## License
|
66
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Distributable'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
require 'bundler/gem_tasks'
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Distributable</title>
|
5
|
+
<%= stylesheet_link_tag "distributable/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "distributable/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "distributable/engine"
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Distributable
|
5
|
+
module DistributedNode
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
#
|
13
|
+
# this install the logic and kick started the operation
|
14
|
+
#
|
15
|
+
def distributable(opts = {})
|
16
|
+
options = {
|
17
|
+
dist_key: :identifier,
|
18
|
+
skipped: %w(created_at updated_at created_by updated_by id),
|
19
|
+
change_log_table: "change_logs",
|
20
|
+
sync_log_table: "sync_ogs",
|
21
|
+
log: Logger.new(STDOUT)
|
22
|
+
}
|
23
|
+
|
24
|
+
options.merge!(opts)
|
25
|
+
|
26
|
+
self.primary_key = options[:dist_key].to_sym
|
27
|
+
|
28
|
+
before_create :generate_identifier
|
29
|
+
|
30
|
+
after_create :log_new
|
31
|
+
after_update :log_update
|
32
|
+
after_destroy :log_destroy
|
33
|
+
|
34
|
+
#validates options[:dist_key].to_sym, uniqueness: true
|
35
|
+
@@options = options
|
36
|
+
end
|
37
|
+
|
38
|
+
def options
|
39
|
+
@@options
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
# instance methods
|
45
|
+
def distributed?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_identifier
|
50
|
+
if self.has_attribute?(:identifier)
|
51
|
+
while(true)
|
52
|
+
self.identifier = SecureRandom.uuid
|
53
|
+
cnt = eval("#{self.class.name}.where([\"identifier = ?\",'#{self.identifier}']).count")
|
54
|
+
break if cnt == 0
|
55
|
+
end
|
56
|
+
else
|
57
|
+
STDERR.puts "Identifier field does not exist in schema"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
def log_new
|
64
|
+
opts = self.class.options
|
65
|
+
if ActiveRecord::Base.connection.table_exists?(opts[:change_log_table].to_s)
|
66
|
+
log = eval("#{opts[:change_log_table].to_s.classify}.new")
|
67
|
+
log.table_name = self.class.table_name
|
68
|
+
log.key = self.send opts[:dist_key]
|
69
|
+
log.operation = 1
|
70
|
+
log.save
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def log_update
|
75
|
+
opts = self.class.options
|
76
|
+
if ActiveRecord::Base.connection.table_exists?(opts[:change_log_table].to_s)
|
77
|
+
if self.changed.length > 0
|
78
|
+
changed = self.changed.delete_if { |c| opts[:skipped].include?(c) }
|
79
|
+
if changed.length > 0
|
80
|
+
|
81
|
+
log = eval("#{opts[:change_log_table].to_s.classify}.new")
|
82
|
+
log.table_name = self.class.table_name
|
83
|
+
log.key = self.send opts[:dist_key]
|
84
|
+
log.changed_fields = changed.to_yaml
|
85
|
+
log.operation = 2
|
86
|
+
log.save
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def log_destroy
|
94
|
+
opts = self.class.options
|
95
|
+
if ActiveRecord::Base.connection.table_exists?(opts[:change_log_table].to_s)
|
96
|
+
log = eval("#{opts[:change_log_table].to_s.classify}.new")
|
97
|
+
log.table_name = self.class.table_name
|
98
|
+
log.key = self.send opts[:dist_key]
|
99
|
+
log.operation = 3
|
100
|
+
log.save
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Distributable
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Distributable
|
4
|
+
|
5
|
+
config.to_prepare do
|
6
|
+
# include into ApplicationController
|
7
|
+
#ActionController::Base.send :include, Canopus::Concerns::Controllers::Authenticator
|
8
|
+
ActiveRecord::Base.send :include, Distributable::DistributedNode
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# desc "Explaining what the task does"
|
2
|
+
# task :distributable do
|
3
|
+
# # Task goes here
|
4
|
+
# end
|
5
|
+
|
6
|
+
namespace "distributable" do
|
7
|
+
desc "Setup the environment for distributable to work"
|
8
|
+
task :setup do
|
9
|
+
#out = Thread.new do
|
10
|
+
# create change log table
|
11
|
+
STDOUT.puts `rails g model change_log table_name:string key:string operation:integer changed_fields:text`
|
12
|
+
STDOUT.flush
|
13
|
+
STDOUT.puts "Distributable based model generated..."
|
14
|
+
STDOUT.flush
|
15
|
+
STDOUT.puts `rails db:migrate`
|
16
|
+
STDOUT.flush
|
17
|
+
#end
|
18
|
+
|
19
|
+
#i = Thread.new do
|
20
|
+
# $stdin.each_line do |l|
|
21
|
+
# STDOUT.puts l
|
22
|
+
# end
|
23
|
+
#end
|
24
|
+
|
25
|
+
# createh sync log table
|
26
|
+
#sl = `rails g model sync_log node_id:string user_id:string`
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: distributable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Liaw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sqlite3
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: ''
|
42
|
+
email:
|
43
|
+
- chrisliaw@antrapol.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- MIT-LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- app/assets/config/distributable_manifest.js
|
52
|
+
- app/assets/javascripts/distributable/application.js
|
53
|
+
- app/assets/stylesheets/distributable/application.css
|
54
|
+
- app/controllers/distributable/application_controller.rb
|
55
|
+
- app/helpers/distributable/application_helper.rb
|
56
|
+
- app/jobs/distributable/application_job.rb
|
57
|
+
- app/mailers/distributable/application_mailer.rb
|
58
|
+
- app/models/distributable/application_record.rb
|
59
|
+
- app/views/layouts/distributable/application.html.erb
|
60
|
+
- config/routes.rb
|
61
|
+
- lib/distributable.rb
|
62
|
+
- lib/distributable/engine.rb
|
63
|
+
- lib/distributable/version.rb
|
64
|
+
- lib/tasks/distributable_tasks.rake
|
65
|
+
homepage: ''
|
66
|
+
licenses:
|
67
|
+
- MIT
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.5.2
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: ''
|
89
|
+
test_files: []
|