active_record_handlersocket 0.0.1
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.
- data/.gitignore +5 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +86 -0
- data/Rakefile +87 -0
- data/active_record_handlersocket.gemspec +35 -0
- data/lib/active_record_handlersocket.rb +13 -0
- data/lib/active_record_handlersocket/active_record_handler_socket.rb +15 -0
- data/lib/active_record_handlersocket/base.rb +5 -0
- data/lib/active_record_handlersocket/connection.rb +44 -0
- data/lib/active_record_handlersocket/finder.rb +104 -0
- data/lib/active_record_handlersocket/manager.rb +46 -0
- data/lib/active_record_handlersocket/version.rb +3 -0
- data/spec/cases/connection_spec.rb +107 -0
- data/spec/cases/finder_spec.rb +313 -0
- data/spec/cases/manager_spec.rb +149 -0
- data/spec/cases/version_spec.rb +7 -0
- data/spec/configuration.rb +26 -0
- data/spec/factories/people.rb +15 -0
- data/spec/mock/hobby.rb +5 -0
- data/spec/mock/person.rb +5 -0
- data/spec/spec_helper.rb +32 -0
- metadata +219 -0
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Takayuki Sugita
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
active_record_handlersocket
|
2
|
+
===========================
|
3
|
+
|
4
|
+
HandlerSocket for ActiveRecord; depends handlersocket gem https://github.com/miyucy/handlersocket
|
5
|
+
|
6
|
+
|
7
|
+
**Underconstruction**
|
8
|
+
|
9
|
+
usage
|
10
|
+
------------------------------------------------------------
|
11
|
+
|
12
|
+
Update your `config/database.yml` of rails project. (Available to set database same as AR read/write database.)
|
13
|
+
|
14
|
+
```
|
15
|
+
development_hs_read:
|
16
|
+
host: localhost
|
17
|
+
port: 9998
|
18
|
+
database: active_record_handler_socket
|
19
|
+
```
|
20
|
+
|
21
|
+
Define HandlerSocket index setting on your ActiveReocrd Model.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class Person < ActiveRecord::Base
|
25
|
+
handlersocket :id, "PRIMARY", %W[id name age]
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
Call `hsfind_by_#{key}` of `hsfind_multi_by_#{key}` to get record(s) as ActiveRecord Object.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Person.hsfind_by_id(1)
|
33
|
+
#=> #<Person id: 1, name: "Bob Marley", age: 36>
|
34
|
+
|
35
|
+
Person.hsfind_multi_by_id(1, 2)
|
36
|
+
#=> [
|
37
|
+
# #<Person id: 1, name: "Bob Marley", age: 36>,
|
38
|
+
# #<Person id: 2, name: "Pharrell Wiiliams", age: 41>
|
39
|
+
# ]
|
40
|
+
```
|
41
|
+
|
42
|
+
|
43
|
+
development
|
44
|
+
------------------------------------------------------------
|
45
|
+
|
46
|
+
Dev dependencies
|
47
|
+
|
48
|
+
```sh
|
49
|
+
mkdir vendor
|
50
|
+
bundle install --path=vendor
|
51
|
+
```
|
52
|
+
|
53
|
+
Prepare DB
|
54
|
+
|
55
|
+
```sh
|
56
|
+
rake db:prepare
|
57
|
+
```
|
58
|
+
|
59
|
+
create following items on MySQL.
|
60
|
+
|
61
|
+
key | value
|
62
|
+
-----------------|------------------------------------------
|
63
|
+
user | rails
|
64
|
+
database (dev) | active_record_handler_socket
|
65
|
+
database (test) | active_record_handler_socket_test
|
66
|
+
tables | people, hobbies
|
67
|
+
|
68
|
+
|
69
|
+
Try example on console
|
70
|
+
|
71
|
+
```sh
|
72
|
+
bundle exec irb
|
73
|
+
```
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
require 'examples/init'
|
77
|
+
#=> true
|
78
|
+
|
79
|
+
Person.create(:name => "Bob Marley", :age => 36, :status => false)
|
80
|
+
|
81
|
+
Person.find_by_id(1)
|
82
|
+
#=> #<Person id: 1, name: "Bob Marley", age: 36, status: false>
|
83
|
+
Person.hsfind_by_id(1)
|
84
|
+
#=> #<Person id: 1, name: "Bob Marley", age: 36, status: false>
|
85
|
+
```
|
86
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
task :default => [:spec]
|
4
|
+
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
+
spec.pattern = "spec/**/*_spec.rb"
|
8
|
+
spec.rspec_opts = ["-cfs"]
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :db do
|
12
|
+
USER = "rails"
|
13
|
+
|
14
|
+
DATABASES = %W[
|
15
|
+
active_record_handler_socket
|
16
|
+
active_record_handler_socket_test
|
17
|
+
]
|
18
|
+
|
19
|
+
TABLES = {
|
20
|
+
:people => %W[
|
21
|
+
id int(11) NOT NULL AUTO_INCREMENT,
|
22
|
+
name varchar(255) DEFAULT '',
|
23
|
+
age int(11) DEFAULT NULL,
|
24
|
+
status tinyint(1) NOT NULL DEFAULT '1',
|
25
|
+
PRIMARY KEY (id)
|
26
|
+
].join(" "),
|
27
|
+
:hobbies => %W[
|
28
|
+
id int(11) NOT NULL AUTO_INCREMENT,
|
29
|
+
person_id int(11) NOT NULL,
|
30
|
+
title varchar(255) DEFAULT '',
|
31
|
+
created_at datetime DEFAULT NULL,
|
32
|
+
updated_at datetime DEFAULT NULL,
|
33
|
+
PRIMARY KEY (id),
|
34
|
+
KEY index_hobbies_on_person_id (person_id)
|
35
|
+
].join(" ")
|
36
|
+
}
|
37
|
+
|
38
|
+
def mysql(query, options = {})
|
39
|
+
_user = options[:user] || USER
|
40
|
+
_db = options[:database]
|
41
|
+
|
42
|
+
puts ""
|
43
|
+
|
44
|
+
begin
|
45
|
+
sh %Q|mysql -u #{_user} #{_db} -e "#{query}"|
|
46
|
+
rescue => e
|
47
|
+
puts e.message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "create user for active_record_handler_socket"
|
52
|
+
task :create_user do
|
53
|
+
mysql "GRANT ALL PRIVILEGES ON *.* TO '#{USER}'@'localhost' WITH GRANT OPTION", :user => "root"
|
54
|
+
mysql "SHOW GRANTS FOR 'rails'@'localhost'", :user => "root"
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "create databases for active_record_handler_socket"
|
58
|
+
task :create_databases do
|
59
|
+
DATABASES.each do |database|
|
60
|
+
mysql "CREATE DATABASE #{database} DEFAULT CHARACTER SET 'utf8'"
|
61
|
+
end
|
62
|
+
|
63
|
+
mysql "SHOW DATABASES"
|
64
|
+
end
|
65
|
+
|
66
|
+
desc "create tables for active_record_handler_socket"
|
67
|
+
task :create_tables do
|
68
|
+
DATABASES.each do |database|
|
69
|
+
TABLES.each do |table, schema|
|
70
|
+
mysql "CREATE TABLE #{table} (#{schema}) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8", :database => database
|
71
|
+
end
|
72
|
+
|
73
|
+
mysql "SHOW TABLES", :database => database
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
desc "run db tasks}"
|
78
|
+
task :prepare do
|
79
|
+
%W[
|
80
|
+
db:create_user
|
81
|
+
db:create_databases
|
82
|
+
db:create_tables
|
83
|
+
].each do |task|
|
84
|
+
Rake::Task[task].invoke
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "active_record_handlersocket/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "active_record_handlersocket"
|
7
|
+
s.version = ActiveRecordHandlersocket::VERSION
|
8
|
+
s.authors = ["Takayuki Sugita"]
|
9
|
+
s.email = ["sugilog@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/sugilog/active_record_handlersocket"
|
11
|
+
s.summary = %q{HandlerSocket for ActiveRecord}
|
12
|
+
s.description = %q{Easy-to-use handlersocket from existing ActiveRecord Models}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n").select{|f| f !~ /^examples\// }
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# specify any dependencies here; for example:
|
20
|
+
s.add_runtime_dependency "activerecord", "~> 2.3.12"
|
21
|
+
s.add_runtime_dependency "handlersocket", "~> 0.0.2"
|
22
|
+
s.add_development_dependency 'rake', '~> 0.9.2.2'
|
23
|
+
|
24
|
+
if RUBY_VERSION >= "1.8.7"
|
25
|
+
s.add_development_dependency "mysql2"
|
26
|
+
s.add_development_dependency "rspec"
|
27
|
+
s.add_development_dependency "factory_girl"
|
28
|
+
s.add_development_dependency "database_cleaner"
|
29
|
+
else
|
30
|
+
s.add_development_dependency "mysql2", '0.2.18'
|
31
|
+
s.add_development_dependency "rspec", "~> 2.11.0"
|
32
|
+
s.add_development_dependency "factory_girl", "2.3.2"
|
33
|
+
s.add_development_dependency "database_cleaner", "0.9.1"
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveRecordHandlerSocket
|
2
|
+
def self.included(c)
|
3
|
+
[
|
4
|
+
Manager,
|
5
|
+
Finder,
|
6
|
+
Connection
|
7
|
+
].each do |_module|
|
8
|
+
c.extend _module
|
9
|
+
c.extend _module::PrivateMethods
|
10
|
+
c.private_class_method *_module::PrivateMethods.instance_methods(false)
|
11
|
+
end
|
12
|
+
|
13
|
+
c.__send__ :hs_establish_connection
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ActiveRecordHandlerSocket
|
2
|
+
module Connection
|
3
|
+
def hs_reconnect!
|
4
|
+
hs_read_connection.reconnect
|
5
|
+
hs_reset_opened_indexes
|
6
|
+
hs_active?
|
7
|
+
end
|
8
|
+
|
9
|
+
# XXX: stable_point cannot return correct status before open_index.
|
10
|
+
# connection establish with unknown port -> call stable_point -> retrun true
|
11
|
+
def hs_active?
|
12
|
+
[
|
13
|
+
hs_read_connection.stable_point
|
14
|
+
].all?
|
15
|
+
end
|
16
|
+
|
17
|
+
module PrivateMethods
|
18
|
+
mattr_reader :hs_connections
|
19
|
+
@@hs_connections = {}
|
20
|
+
|
21
|
+
# TODO: writeread connection
|
22
|
+
def hs_establish_connection(name = nil)
|
23
|
+
case name
|
24
|
+
when nil
|
25
|
+
hs_establish_connection("#{RAILS_ENV}_hs_read")
|
26
|
+
else
|
27
|
+
if config = ActiveRecord::Base.configurations[name]
|
28
|
+
config = config.symbolize_keys
|
29
|
+
|
30
|
+
@@hs_connections.update(
|
31
|
+
:read => HandlerSocket.new(:host => config[:host], :port => config[:port].to_s)
|
32
|
+
)
|
33
|
+
else
|
34
|
+
raise ArgumentError, "unknown configuration: #{name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def hs_read_connection
|
40
|
+
@@hs_connections[:read]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ActiveRecordHandlerSocket
|
2
|
+
class CannotConnectError < StandardError; end
|
3
|
+
|
4
|
+
module Finder
|
5
|
+
def method_missing(method_name, *args, &block)
|
6
|
+
case method_name.to_s
|
7
|
+
when /^hsfind_(by|multi_by)_([_a-zA-Z]\w*)$/
|
8
|
+
finder = :first if $1 == "by"
|
9
|
+
finder = :multi if $1 == "multi_by"
|
10
|
+
key = $2
|
11
|
+
hsfind(finder, key, args)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def hsfind(finder, key, args)
|
18
|
+
index_key = hs_index_key(key)
|
19
|
+
setting = hs_fetch_key(index_key)
|
20
|
+
|
21
|
+
options = args.extract_options!
|
22
|
+
id = setting[:id]
|
23
|
+
operator = options[:operator] || "="
|
24
|
+
|
25
|
+
hs_open_index(index_key)
|
26
|
+
|
27
|
+
case finder
|
28
|
+
# XXX: experimental
|
29
|
+
when :multi
|
30
|
+
_args = args.map{|arg|
|
31
|
+
_arg = []
|
32
|
+
_arg << setting[:id]
|
33
|
+
_arg << operator
|
34
|
+
_arg << [arg]
|
35
|
+
_arg << options[:limit] if options[:limit]
|
36
|
+
_arg
|
37
|
+
}
|
38
|
+
|
39
|
+
results = hs_read_connection.execute_multi(_args)
|
40
|
+
|
41
|
+
results.map{|result|
|
42
|
+
hs_instantiate(index_key, result)
|
43
|
+
}.flatten
|
44
|
+
when :first
|
45
|
+
result = hs_read_connection.execute_single(setting[:id], operator, args)
|
46
|
+
hs_instantiate(index_key, result).first
|
47
|
+
else
|
48
|
+
# XXX: Not Support
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module PrivateMethods
|
53
|
+
def hs_open_index(index_key)
|
54
|
+
setting = hs_fetch_key(index_key)
|
55
|
+
|
56
|
+
if setting[:opened]
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
config = configurations["#{RAILS_ENV}_hs_read"].symbolize_keys
|
61
|
+
|
62
|
+
id = setting[:id]
|
63
|
+
database = config[:database]
|
64
|
+
table = table_name
|
65
|
+
index = setting[:index]
|
66
|
+
fields = setting[:fields].join(",")
|
67
|
+
|
68
|
+
signal = hs_read_connection.open_index(id, database, table, index, fields)
|
69
|
+
|
70
|
+
case
|
71
|
+
when signal == 0
|
72
|
+
setting[:opened] = true
|
73
|
+
when signal > 0
|
74
|
+
error = hs_read_connection.error
|
75
|
+
raise ArgumentError, "invalid setting given: #{error}"
|
76
|
+
else
|
77
|
+
hs_reset_opened_indexes
|
78
|
+
error = hs_read_connection.error
|
79
|
+
raise ActiveRecordHandlerSocket::CannotConnectError, "connection lost: #{error}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def hs_instantiate(index_key, result_on_single)
|
84
|
+
signal, result = result_on_single
|
85
|
+
|
86
|
+
case
|
87
|
+
when signal == 0
|
88
|
+
setting = hs_fetch_key(index_key)
|
89
|
+
fields = setting[:fields]
|
90
|
+
|
91
|
+
result.map do |record|
|
92
|
+
attrs = Hash[ *fields.zip(record).flatten ]
|
93
|
+
instantiate(attrs)
|
94
|
+
end
|
95
|
+
when signal > 0
|
96
|
+
raise ArgumentError, "invalid argument given: #{result}"
|
97
|
+
else
|
98
|
+
hs_reset_opened_indexes
|
99
|
+
raise ActiveRecordHandlerSocket::CannotConnectError, result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveRecordHandlerSocket
|
2
|
+
class UnknownIndexError < StandardError; end
|
3
|
+
|
4
|
+
module Manager
|
5
|
+
module PrivateMethods
|
6
|
+
mattr_reader :hs_indexes, :hs_index_count_cache
|
7
|
+
@@hs_indexes = {}
|
8
|
+
@@hs_index_count_cache = 0
|
9
|
+
|
10
|
+
def handlersocket(key, index, fields)
|
11
|
+
index_key = hs_index_key(key)
|
12
|
+
|
13
|
+
if @@hs_indexes.has_key?(index_key)
|
14
|
+
warn "#{self.name} handlersocket: #{key} was updated"
|
15
|
+
end
|
16
|
+
|
17
|
+
@@hs_indexes.update(
|
18
|
+
index_key => {
|
19
|
+
:id => hs_index_count,
|
20
|
+
:index => index,
|
21
|
+
:fields => fields,
|
22
|
+
:opened => false
|
23
|
+
}
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def hs_index_key(key)
|
28
|
+
[self.name, key].join(":")
|
29
|
+
end
|
30
|
+
|
31
|
+
def hs_fetch_key(index_key)
|
32
|
+
@@hs_indexes[index_key] or raise UnknownIndexError, "unknown key given: #{index_key}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def hs_index_count
|
36
|
+
@@hs_index_count_cache += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def hs_reset_opened_indexes
|
40
|
+
@@hs_indexes.each do |_, setting|
|
41
|
+
setting[:opened] = false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|