curator 0.1.0
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/lib/curator/migration.rb +9 -0
- data/lib/curator/migrator.rb +29 -0
- data/lib/curator/model.rb +44 -0
- data/lib/curator/railtie.rb +9 -0
- data/lib/curator/repository.rb +163 -0
- data/lib/curator/riak/data_store.rb +73 -0
- data/lib/curator.rb +17 -0
- metadata +95 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Curator
|
2
|
+
class Migrator
|
3
|
+
def initialize(collection_name)
|
4
|
+
@collection_name = collection_name
|
5
|
+
end
|
6
|
+
|
7
|
+
def migrate(attributes)
|
8
|
+
migrations = _applicable_migrations(attributes["version"].to_i)
|
9
|
+
migrations.inject(attributes) do |migrated_attributes, migration|
|
10
|
+
migration.migrate(migrated_attributes).merge("version" => migration.version)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def _applicable_migrations(current_version)
|
15
|
+
@applicable_migrations ||= _all_migrations.select { |migration| migration.version > current_version }.sort_by(&:version)
|
16
|
+
end
|
17
|
+
|
18
|
+
def _all_migrations
|
19
|
+
files = Dir.glob("#{File.join(Curator.migrations_path, @collection_name)}/*.rb")
|
20
|
+
|
21
|
+
files.map do |file|
|
22
|
+
load file
|
23
|
+
migration_version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
24
|
+
klass = name.camelize.constantize
|
25
|
+
klass.new(migration_version.to_i)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_model'
|
3
|
+
|
4
|
+
module Curator
|
5
|
+
module Model
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ActiveModel::Conversion
|
8
|
+
|
9
|
+
included do
|
10
|
+
attr_accessor :created_at, :updated_at
|
11
|
+
attr_writer :version
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(args = {})
|
15
|
+
args.each do |attribute, value|
|
16
|
+
send("#{attribute}=", value) if respond_to?("#{attribute}=")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def persisted?
|
21
|
+
id.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def version
|
25
|
+
@version || self.class.version
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
self.id == other.id
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
include ActiveModel::Naming
|
34
|
+
|
35
|
+
def current_version(number)
|
36
|
+
@version = number
|
37
|
+
end
|
38
|
+
|
39
|
+
def version
|
40
|
+
@version || 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Curator
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "railtie.configure_rails_initialization" do
|
4
|
+
Curator.environment = Rails.env
|
5
|
+
Curator.riak_config_file = Rails.root.join('config', 'riak.yml')
|
6
|
+
Curator.migrations_path = Rails.root.join('db', 'migrate')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'active_support/core_ext/object/instance_variables'
|
3
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Curator
|
7
|
+
module Repository
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def collection_name
|
12
|
+
ActiveSupport::Inflector.tableize(klass)
|
13
|
+
end
|
14
|
+
|
15
|
+
def data_store
|
16
|
+
@data_store ||= Riak::DataStore
|
17
|
+
end
|
18
|
+
|
19
|
+
def data_store=(store)
|
20
|
+
@data_store = store
|
21
|
+
end
|
22
|
+
|
23
|
+
def delete(object)
|
24
|
+
data_store.delete(collection_name, object.id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def encrypted_entity
|
28
|
+
@encrypted_entity = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_by_created_at(start_time, end_time)
|
32
|
+
_find_by_index(collection_name, :created_at, _format_time_for_index(start_time).._format_time_for_index(end_time))
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_by_updated_at(start_time, end_time)
|
36
|
+
_find_by_index(collection_name, :updated_at, _format_time_for_index(start_time).._format_time_for_index(end_time))
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_by_id(id)
|
40
|
+
if hash = data_store.find_by_key(collection_name, id)
|
41
|
+
_deserialize(hash[:key], hash[:data])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def indexed_fields(*fields)
|
46
|
+
@indexed_fields = fields
|
47
|
+
|
48
|
+
@indexed_fields.each do |field_name|
|
49
|
+
_build_finder_methods(field_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def klass
|
54
|
+
name.to_s.gsub("Repository", "").constantize
|
55
|
+
end
|
56
|
+
|
57
|
+
def migrator
|
58
|
+
@migrator ||= Curator::Migrator.new(collection_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def save(object)
|
62
|
+
hash = {
|
63
|
+
:collection_name => collection_name,
|
64
|
+
:value => _serialize(object),
|
65
|
+
:index => _indexes(object)
|
66
|
+
}
|
67
|
+
|
68
|
+
if object.id
|
69
|
+
hash[:key] = object.id
|
70
|
+
data_store.save(hash)
|
71
|
+
else
|
72
|
+
object.id = data_store.save(hash).key
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def serialize(object)
|
77
|
+
object.instance_values
|
78
|
+
end
|
79
|
+
|
80
|
+
def _build_finder_methods(field_name)
|
81
|
+
singleton_class.class_eval do
|
82
|
+
define_method("find_by_#{field_name}") do |value|
|
83
|
+
_find_by_index(collection_name, field_name, value)
|
84
|
+
end
|
85
|
+
define_method("find_first_by_#{field_name}") do |value|
|
86
|
+
_find_by_index(collection_name, field_name, value).first
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def _find_by_index(collection_name, field_name, value)
|
92
|
+
if results = data_store.find_by_index(collection_name, field_name, value)
|
93
|
+
results.map do |hash|
|
94
|
+
_deserialize(hash[:key], hash[:data])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def deserialize(attributes)
|
100
|
+
klass.new(attributes)
|
101
|
+
end
|
102
|
+
|
103
|
+
def _deserialize(id, data)
|
104
|
+
attributes = data.with_indifferent_access
|
105
|
+
migrated_attributes = migrator.migrate(attributes)
|
106
|
+
object = deserialize(migrated_attributes)
|
107
|
+
object.id = id
|
108
|
+
object.created_at = Time.parse(attributes[:created_at]) if attributes[:created_at].present?
|
109
|
+
object.updated_at = Time.parse(attributes[:updated_at]) if attributes[:updated_at].present?
|
110
|
+
object
|
111
|
+
end
|
112
|
+
|
113
|
+
def _encrypted_attributes(object, attributes)
|
114
|
+
return attributes unless _encrypted_entity?
|
115
|
+
|
116
|
+
encryption_key = EncryptionKeyRepository.find_active
|
117
|
+
plaintext = attributes.to_json
|
118
|
+
ciphertext = encryption_key.encrypt(plaintext)
|
119
|
+
{
|
120
|
+
:encryption_key_id => encryption_key.id,
|
121
|
+
:encrypted_data => Base64.encode64(ciphertext)
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def _encrypted_entity?
|
126
|
+
@encrypted_entity == true
|
127
|
+
end
|
128
|
+
|
129
|
+
def _format_time_for_index(time)
|
130
|
+
time.to_json.gsub('"', '')
|
131
|
+
end
|
132
|
+
|
133
|
+
def _indexed_fields
|
134
|
+
@indexed_fields || []
|
135
|
+
end
|
136
|
+
|
137
|
+
def _indexes(object)
|
138
|
+
index_values = _indexed_fields.map { |field| [field, object.send(field)] }
|
139
|
+
index_values += [
|
140
|
+
[:created_at, _format_time_for_index(object.send(:created_at))],
|
141
|
+
[:updated_at, _format_time_for_index(object.send(:updated_at))]
|
142
|
+
]
|
143
|
+
Hash[index_values]
|
144
|
+
end
|
145
|
+
|
146
|
+
def _serialize(object)
|
147
|
+
attributes = serialize(object).reject { |key, val| val.nil? }
|
148
|
+
|
149
|
+
timestamp = Time.now.utc
|
150
|
+
|
151
|
+
updated_at = timestamp
|
152
|
+
created_at = object.created_at || timestamp
|
153
|
+
|
154
|
+
object.created_at = created_at
|
155
|
+
object.updated_at = updated_at
|
156
|
+
attributes[:created_at] = created_at
|
157
|
+
attributes[:updated_at] = updated_at
|
158
|
+
|
159
|
+
_encrypted_attributes(object, attributes)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'riak'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class DataStore
|
6
|
+
BUCKET_PREFIX = "curator"
|
7
|
+
|
8
|
+
def self.client
|
9
|
+
return @client if @client
|
10
|
+
yml_config = YAML.load(File.read(Curator.riak_config_file))[Curator.environment]
|
11
|
+
@client = Riak::Client.new(yml_config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.delete(bucket_name, key)
|
15
|
+
bucket = _bucket(bucket_name)
|
16
|
+
object = bucket.get(key)
|
17
|
+
object.delete
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.ping
|
21
|
+
client.ping
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.save(options)
|
25
|
+
bucket = _bucket(options[:collection_name])
|
26
|
+
object = Riak::RObject.new(bucket, options[:key])
|
27
|
+
object.content_type = "application/json"
|
28
|
+
object.data = options[:value]
|
29
|
+
options.fetch(:index, {}).each do |index_name, index_value|
|
30
|
+
object.indexes["#{index_name}_bin"] << index_value
|
31
|
+
end
|
32
|
+
object.store
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.find_by_key(bucket_name, key)
|
36
|
+
bucket = _bucket(bucket_name)
|
37
|
+
begin
|
38
|
+
object = bucket.get(key)
|
39
|
+
{ :key => object.key, :data => object.data } unless object.data.empty?
|
40
|
+
rescue Riak::HTTPFailedRequest => failed_request
|
41
|
+
raise failed_request unless failed_request.not_found?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.find_by_index(bucket_name, index_name, query)
|
46
|
+
return [] if query.nil?
|
47
|
+
|
48
|
+
bucket = _bucket(bucket_name)
|
49
|
+
begin
|
50
|
+
keys = _find_key_by_index(bucket, index_name.to_s, query)
|
51
|
+
keys.map { |key| find_by_key(bucket_name, key) }
|
52
|
+
rescue Riak::HTTPFailedRequest => failed_request
|
53
|
+
raise failed_request unless failed_request.not_found?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self._bucket(name)
|
58
|
+
client.bucket(_bucket_name(name))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self._bucket_name(name)
|
62
|
+
bucket_prefix + ":" + name
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.bucket_prefix
|
66
|
+
"#{BUCKET_PREFIX}:#{Curator.environment}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self._find_key_by_index(bucket, index_name, query)
|
70
|
+
bucket.get_index("#{index_name}_bin", query)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/curator.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'curator/migration'
|
4
|
+
require 'curator/migrator'
|
5
|
+
require 'curator/model'
|
6
|
+
require 'curator/repository'
|
7
|
+
require 'curator/riak/data_store'
|
8
|
+
require 'curator/railtie' if defined?(Rails)
|
9
|
+
|
10
|
+
module Curator
|
11
|
+
class << self
|
12
|
+
attr_accessor :environment, :migrations_path, :riak_config_file
|
13
|
+
end
|
14
|
+
|
15
|
+
self.environment = "development"
|
16
|
+
self.riak_config_file = File.expand_path(File.dirname(__FILE__) + "/../config/riak.yml")
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: curator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Braintree
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: &70110328955160 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70110328955160
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activemodel
|
27
|
+
requirement: &70110328954240 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70110328954240
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
requirement: &70110328953820 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70110328953820
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: riak-client
|
49
|
+
requirement: &70110328952900 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70110328952900
|
58
|
+
description: Model and repository framework
|
59
|
+
email: code@getbraintree.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- lib/curator/migration.rb
|
65
|
+
- lib/curator/migrator.rb
|
66
|
+
- lib/curator/model.rb
|
67
|
+
- lib/curator/railtie.rb
|
68
|
+
- lib/curator/repository.rb
|
69
|
+
- lib/curator/riak/data_store.rb
|
70
|
+
- lib/curator.rb
|
71
|
+
homepage: http://github.com/braintree/curator
|
72
|
+
licenses: []
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.10
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Model and repository framework
|
95
|
+
test_files: []
|