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.
@@ -0,0 +1,9 @@
1
+ module Curator
2
+ class Migration
3
+ attr_accessor :version
4
+
5
+ def initialize(version)
6
+ @version = version
7
+ end
8
+ end
9
+ end
@@ -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: []