activerecord-crate-adapter 0.0.1

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: 56df652b663e2d0dd2abcc83154b07b1575ee21b
4
+ data.tar.gz: f9c8908ea5f6f296d05a16f4502ce12f430d8921
5
+ SHA512:
6
+ metadata.gz: bf7d9a0a55a82065e81fbb3f9a1406e8524b81fef1f8803c0b0594eb4136b26a0af7fc20cf44439fca7390f49b42c4a6e7fde56dfc332c7a38baa9c9fb0689ff
7
+ data.tar.gz: 6d0d522e9680e251477fada311f1a023ce349a507e43e13f668ee1210d2f7173997885e5da312c9eea957c5fddef827a88e35e24cc2c8f2cda25c87e441041ae
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
19
+ .rvmrc
20
+ crate_test_server*
21
+ *.log
22
+ log/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord-crate-adapter.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Christoph Klocker
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # Activerecord::Crate::Adapter
2
+
3
+ The [Crate](http://www.crate.io) adapter for ActiveRecord.
4
+
5
+ ## Work in progress
6
+
7
+ I've just started coding the adapter and lots of functionality might still not work. Give it a try
8
+ and help bei either contributing (fix it) or add an issue.
9
+
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'activerecord-crate-adapter', :git => "https://github.com/crate/activerecord-crate-adapter.git"
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install activerecord-crate-adapter
24
+
25
+ ## Usage
26
+
27
+ When using Rails update your database.yml
28
+
29
+ development:
30
+ adapter: crate
31
+ host: 127.0.0.1
32
+ port: 4200
33
+
34
+ Crate doesn't come with an autoincrement feature for your model ids. So you need to set
35
+ it yourself. One way is to use SecureRandom.uuid, if you think there is a better one,
36
+ please add an issue so we can discuss.
37
+
38
+ class Post < ActiveRecord::Base
39
+
40
+ before_validation :set_id
41
+
42
+ private
43
+
44
+ def set_id
45
+ self.id = SecureRandom.uuid
46
+ end
47
+
48
+ end
49
+
50
+ ## Special Data Types
51
+
52
+ ### Array
53
+ You can simply create Array columns by specifying t.array and passing array_type when you create a migration. You need at least the upcoming
54
+ release 0.39 of Crate for this functionality.
55
+
56
+ t.array :tags, array_type: :string
57
+ t.array :votes, array_type: :integer
58
+ t.array :bool_arr, array_type: :boolean
59
+
60
+ When you create an object just pass your Array directly
61
+
62
+ Post.create!(title: 'Arrays are awesome', tags: %w(hot fresh), votes: [1,2])
63
+ post = Post.where("'fresh' = ANY (tags)")
64
+
65
+ ### Object
66
+ Crate allows you to define nested objects. You need at least the upcoming
67
+ release 0.39 of Crate for this functionality. I tried to make it as simply as possible to use and reuse existing AR functionality,
68
+ I therefore ask you to reuse the existing serialize functionality. AR#serialize allows you to define your own serialization
69
+ mechanism and we simply reuse that for serializing an AR object. To get serialize working simply create a #dump and #load method
70
+ on the class that creates a literal statement that is then used in the SQL. Read up more in this [commit}(https://github.com/crate/crate/commit/16a3d4b3f23996a327f91cdacef573f7ba946017).
71
+
72
+ I tried to make your guys life easier and created a module that does this automatically for you. Simply make all attributes accessible
73
+ and assign it in the initializer. So a serialized class should look like this:
74
+
75
+ require 'active_record/attribute_methods/crate_object'
76
+
77
+ class Address
78
+ attr_accessor :street, :city, :phones, :zip
79
+
80
+ include CrateObject
81
+
82
+ def initialize(opts)
83
+ @street = opts[:street]
84
+ @city = opts[:city]
85
+ @phones = opts[:phones]
86
+ @zip = opts[:zip]
87
+ end
88
+
89
+ end
90
+
91
+ Check out CrateObject module if you need to write your own serializer.
92
+
93
+ Then in your model simply use #serialize to have objects working
94
+
95
+ class User < ActiveRecord::Base
96
+ serialize :address, Address
97
+ end
98
+
99
+ Note: I do not plan to support nested objects inside objects.
100
+
101
+ #### Object Migrations
102
+
103
+ In the migrations you can create an object and specify the object behaviour(strict|dynamic|ignored) and it's schema.
104
+
105
+ t.object :address, object_schema_behaviour: :strict,
106
+ object_schema: {street: :string, city: :string, phones: {array: :string}, zip: :integer}
107
+
108
+
109
+
110
+ ## Migrations
111
+
112
+ Currently adding and dropping indices is not support by Crate. Issue [#733](https://github.com/crate/crate/issues/733)
113
+
114
+ # not supported by Crate yet
115
+ add_index :posts, :comment_count
116
+ remove_index :posts, :comment_count
117
+
118
+
119
+ ## Gotchas
120
+
121
+ Crate is eventually consistent, that means if you create a record and query for it right away it
122
+ won't work (except queries for the primary key!). Read more about it [here](https://github.com/crate/crate/blob/master/docs/sql/dml.txt#L569)
123
+
124
+ Crate does not support Joins (yet) so joins won't work.
125
+
126
+ ## Tests
127
+
128
+ First run the test instance of crate
129
+
130
+ $ ./spec/test_server.rb
131
+
132
+ then run the tests
133
+
134
+ $ rspec spec
135
+
136
+ ## Contributing
137
+
138
+ This adapter is a work in progress. If you think something is missing, either follow the steps below
139
+ or log a new issue, so someone else can tackle it.
140
+
141
+ 1. Fork it ( `http://github.com/crate/activerecord-crate-adapter/fork` )
142
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
143
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
144
+ 4. Add tests
145
+ 5. Push to the branch (`git push origin my-new-feature`)
146
+ 6. Create new Pull Request
147
+
148
+ ##Maintainer
149
+
150
+ * [Christoph Klocker](http://www.vedanova.com), [@corck](http://www.twitter.com/corck)
151
+
152
+ ##License
153
+ MIT License. Copyright 2014 Christoph Klocker. [http://vedanova.com](http://vedanova.com)
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'debugger'
3
+
4
+ desc "Open an irb session preloaded with this library"
5
+ task :console do
6
+ sh "bundle exec irb -rubygems -I lib -r ./lib/activerecord-crate-adapter.rb"
7
+ end
8
+
9
+
@@ -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 'activerecord-crate-adapter/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "activerecord-crate-adapter"
8
+ spec.version = ActiverecordCrateAdapter::VERSION
9
+ spec.authors = ["Christoph Klocker"]
10
+ spec.email = ["christoph@vedanova.com"]
11
+ spec.summary = "ActiveRecord Crate Data Adapter"
12
+ spec.description = "ActiveRecord adapter for Crate, a shared-nothing, fully searchable, document-oriented cluster data store."
13
+ spec.homepage = "https://crate.io"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ #spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "debugger"
24
+ spec.add_development_dependency "rspec", "~> 2.14"
25
+
26
+ spec.add_dependency('activerecord', '>= 4.0.0')
27
+ spec.add_dependency('arel', '>= 4.0.0')
28
+ spec.add_dependency('crate_ruby', '~> 0.0.5')
29
+
30
+ end
data/history.txt ADDED
@@ -0,0 +1,9 @@
1
+ # coding: UTF-8
2
+
3
+ === 0.0.1
4
+
5
+ Initial Release
6
+
7
+ * Array data types
8
+ * Supports regular data types
9
+ * Supports Migrations
@@ -0,0 +1,31 @@
1
+ module CrateObject
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def load(object)
6
+ case object
7
+ when String
8
+ object.gsub!('=', ':')
9
+ object = JSON.parse("{#{object}}")
10
+ end
11
+ new(object.symbolize_keys)
12
+ end
13
+
14
+ def dump(object)
15
+ object ? object.to_literals : nil
16
+ end
17
+ end
18
+
19
+
20
+ def to_literals
21
+ arr = []
22
+ instance_variables.each do |var|
23
+ v = instance_variable_get(var)
24
+ value = v.is_a?(Array) ? v : %Q{"#{v}"}
25
+ arr << %Q{"#{var.to_s.gsub(/@/, '')}"=#{value}}
26
+ end
27
+ arr.join(', ')
28
+ end
29
+ end
30
+
31
+
@@ -0,0 +1,59 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module DatabaseStatements
4
+ def exec_query(sql, name = 'SQL', binds = [])
5
+ result = nil
6
+ log(sql, name, binds) {
7
+ result = do_exec_query(sql, name, binds)
8
+ }
9
+ fields = result.cols
10
+ ActiveRecord::Result.new(fields, result.values)
11
+ end
12
+
13
+ # Converts an arel AST to SQL
14
+ def to_sql(arel, binds = [])
15
+ if arel.respond_to?(:ast)
16
+ binds = binds.dup
17
+ visitor.accept(arel.ast) do
18
+ quote(*binds.shift.reverse)
19
+ end
20
+ else
21
+ arel
22
+ end
23
+ end
24
+
25
+ def do_exec_query(sql, name, binds)
26
+ params = []
27
+ binds.each_with_index do |(column, value), index|
28
+ ar_column = column.is_a?(ActiveRecord::ConnectionAdapters::Column)
29
+ next if ar_column && column.sql_type == 'timestamp'
30
+ v = value
31
+ quoted_value = ar_column ? quote(v, column) : quote(v, nil)
32
+ params << quoted_value
33
+ end
34
+ params.each { |p| sql.sub!(/(\?)/, p) }
35
+ @connection.execute sql
36
+ end
37
+
38
+ # Returns the statement identifier for the client side cache
39
+ # of statements
40
+ def sql_key(sql)
41
+ sql
42
+ end
43
+
44
+ # Executes an SQL statement, returning a ResultSet object on success
45
+ # or raising a CrateError exception otherwise.
46
+ def execute(sql, name = nil)
47
+ log(sql, name) do
48
+ @connection.execute(sql)
49
+ end
50
+ end
51
+
52
+ protected
53
+ def select(sql, name, binds)
54
+ exec_query(sql, name, binds)
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Crate
4
+ module Quoting
5
+ # Quotes the column value to help prevent
6
+ # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
7
+ def quote(value, column = nil)
8
+ # records are quoted as their primary key
9
+ return value.quoted_id if value.respond_to?(:quoted_id)
10
+
11
+ if value.is_a?(Array)
12
+ "#{value}".gsub('"', "'")
13
+ elsif column.sql_type == 'object'
14
+ "{ #{value} }"
15
+ else
16
+ super(value, column)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,52 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Crate
4
+ class SchemaCreation < AbstractAdapter::SchemaCreation
5
+
6
+ private
7
+
8
+ def add_column_options!(sql, options)
9
+ if options[:array] || options[:column].try(:array)
10
+ sql.gsub!(/(.*)\s(\w+)$/, '\1 array(\2)')
11
+ end
12
+ super(sql, options)
13
+ end
14
+
15
+ end
16
+
17
+ module SchemaStatements
18
+ def primary_key(table_name)
19
+ res = @connection.execute("select constraint_name from information_schema.table_constraints
20
+ where table_name = '#{quote_table_name(table_name)}' and constraint_type = 'PRIMARY_KEY'")
21
+ res[0].try(:first).try(:first)
22
+ end
23
+
24
+ # overriding as Crate does not support "version primary key" syntax. Need to add the column type.
25
+ def initialize_schema_migrations_table
26
+ unless table_exists?('schema_migrations')
27
+ execute("CREATE TABLE schema_migrations (version string primary key INDEX using plain)")
28
+ end
29
+ end
30
+
31
+ def add_index(table_name, column_name, options = {}) #:nodoc:
32
+ puts
33
+ puts "#########"
34
+ puts "Adding indices is currently not supported by Crate"
35
+ puts "See issue: https://github.com/crate/crate/issues/733"
36
+ puts "#########"
37
+ puts
38
+ end
39
+
40
+ def remove_index(table_name, column_name, options = {}) #:nodoc:
41
+ puts
42
+ puts "#########"
43
+ puts "Dropping indices is currently not supported by Crate"
44
+ puts "See issue: https://github.com/crate/crate/issues/733"
45
+ puts "#########"
46
+ puts
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,210 @@
1
+ require 'active_record'
2
+ require 'active_record/base'
3
+ require 'active_record/base'
4
+ require 'arel/arel_crate'
5
+ require 'arel/visitors/bind_visitor'
6
+ require 'active_support/dependencies/autoload'
7
+ require 'active_support/callbacks'
8
+ require 'active_support/core_ext/string'
9
+ require 'active_record/connection_adapters/abstract_adapter'
10
+ require 'active_record/connection_adapters/statement_pool'
11
+ require 'active_record/connection_adapters/column'
12
+ require 'active_record/connection_adapters/crate/schema_statements'
13
+ require 'active_record/connection_adapters/crate/database_statements'
14
+ require 'active_record/connection_adapters/crate/quoting'
15
+ require 'active_support/core_ext/kernel'
16
+
17
+ begin
18
+ require 'crate_ruby'
19
+ rescue LoadError => e
20
+ raise e
21
+ end
22
+
23
+ module ActiveRecord
24
+
25
+ class Base
26
+ def self.crate_connection(config) #:nodoc:
27
+ config = config.symbolize_keys
28
+ ConnectionAdapters::CrateAdapter.new(nil, logger, nil, config)
29
+ end
30
+ end
31
+
32
+ module ConnectionAdapters
33
+ class CrateAdapter < AbstractAdapter
34
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
35
+ attr_accessor :array, :object
36
+ end
37
+
38
+ include Crate::SchemaStatements
39
+ include DatabaseStatements
40
+ include Crate::Quoting
41
+
42
+ ADAPTER_NAME = 'Crate'.freeze
43
+
44
+ def schema_creation # :nodoc:
45
+ Crate::SchemaCreation.new self
46
+ end
47
+
48
+ NATIVE_DATABASE_TYPES = {
49
+ boolean: {name: "boolean"},
50
+ string: {name: "string"},
51
+ integer: {name: "integer"},
52
+ float: {name: "float"},
53
+ binary: {name: "byte"},
54
+ datetime: {name: "timestamp"},
55
+ timestamp: {name: "timestamp"},
56
+ object: {name: "object"},
57
+ array: {name: "array"}
58
+ }
59
+
60
+ class BindSubstitution < Arel::Visitors::Crate # :nodoc:
61
+ include Arel::Visitors::BindVisitor
62
+ end
63
+
64
+ def initialize(connection, logger, pool, config)
65
+ @port = config[:port]
66
+ @host = config[:host]
67
+ super(connection, logger, pool)
68
+ @schema_cache = SchemaCache.new self
69
+ @visitor = Arel::Visitors::Crate.new self
70
+ @quoted_column_names = {}
71
+ connect
72
+ end
73
+
74
+ def adapter_name
75
+ ADAPTER_NAME
76
+ end
77
+
78
+ # Adds `:array` option to the default set provided by the
79
+ # AbstractAdapter
80
+ def prepare_column_options(column, types)
81
+ spec = super
82
+ spec[:array] = 'true' if column.respond_to?(:array) && column.array
83
+ spec
84
+ end
85
+
86
+ # Adds `:array` as a valid migration key
87
+ def migration_keys
88
+ super + [:array, :object_schema_behaviour, :object_schema_behaviour]
89
+ end
90
+
91
+
92
+ #TODO check what call to use for active
93
+ def active?
94
+ true
95
+ end
96
+
97
+ #TODO
98
+ def clear_cache!
99
+ end
100
+
101
+ #TODO
102
+ def reset!
103
+ end
104
+
105
+ def supports_migrations?
106
+ true
107
+ end
108
+
109
+ def connect
110
+ @connection = CrateRuby::Client.new(["#{@host}:#{@port}"])
111
+ end
112
+
113
+ def columns(table_name) #:nodoc:
114
+ cols = @connection.table_structure(table_name).map do |field|
115
+ CrateColumn.new(field[2], nil, field[4], nil)
116
+ end
117
+ cols
118
+ end
119
+
120
+ def tables
121
+ @connection.tables
122
+ end
123
+
124
+ # def quote_column_name(name) #:nodoc:
125
+ # @quoted_column_names[name] ||= %Q{"#{name.to_s}"}
126
+ # end
127
+
128
+ class CrateColumn < Column
129
+
130
+ def simplified_type(field_type)
131
+ case field_type
132
+ when /_array/i
133
+ :array
134
+ when /object/i
135
+ :object
136
+ else
137
+ super(field_type)
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
144
+
145
+ # Crate doesn't support auto incrementing, therefore we need to manually
146
+ # set a primary key. You need to assure that you always provide an unique
147
+ # id. This might be done via the
148
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
149
+ def primary_key(name, type = :primary_key, options = {})
150
+ options[:primary_key] = true
151
+ column name, "STRING PRIMARY KEY", options
152
+ end
153
+
154
+ def column(name, type = nil, options = {})
155
+ super
156
+ column = self[name]
157
+ column.array = options[:array]
158
+ column.object = options[:object]
159
+ self
160
+ end
161
+
162
+ def object(name, options = {})
163
+ schema_behaviour = options.delete(:object_schema_behaviour)
164
+ type = schema_behaviour ? "object(#{schema_behaviour})" : schema_behaviour
165
+ schema = options.delete(:object_schema)
166
+ type = "#{type} as (#{object_schema_to_string(schema)})" if schema
167
+
168
+ column name, type, options.merge(object: true)
169
+ end
170
+
171
+ def array(name, options = {})
172
+ array_type = options.delete(:array_type)
173
+ raise "Array columns must specify an :array_type (e.g. array_type: :string)" unless array_type.present?
174
+ column name, "array(#{array_type})", options.merge(array: true)
175
+ end
176
+
177
+ private
178
+
179
+ def create_column_definition(name, type)
180
+ ColumnDefinition.new name, type
181
+ end
182
+
183
+ def object_schema_to_string(s)
184
+ ary = []
185
+ s.each_pair do |k, v|
186
+ if v.is_a?(Symbol)
187
+ ary << "#{k} #{v}"
188
+ elsif v.is_a?(Hash)
189
+ a = "array(#{v[:array]})"
190
+ ary << "#{k} #{a}"
191
+ end
192
+ end
193
+ ary.join(', ')
194
+ end
195
+
196
+
197
+ end
198
+
199
+ def create_table_definition(name, temporary, options, as = nil)
200
+ TableDefinition.new native_database_types, name, temporary, options, as
201
+ end
202
+
203
+ def native_database_types
204
+ NATIVE_DATABASE_TYPES
205
+ end
206
+ end
207
+ end
208
+
209
+
210
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_record/connection_adapters/crate_adapter'
2
+ require 'arel/visitors/crate'
@@ -0,0 +1,3 @@
1
+ module ActiverecordCrateAdapter
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ require 'arel'
2
+ require 'arel/visitors/crate'
3
+ require 'arel/visitors/bind_visitor'
@@ -0,0 +1,10 @@
1
+ module Arel
2
+ module Visitors
3
+ class Crate < Arel::Visitors::ToSql
4
+
5
+ private
6
+ end
7
+ end
8
+ end
9
+
10
+ Arel::Visitors::VISITORS['crate'] = Arel::Visitors::Crate
@@ -0,0 +1,26 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe ActiveRecord::ConnectionAdapters::CrateAdapter::TableDefinition do
4
+
5
+
6
+ describe '#object_schema_to_string' do
7
+
8
+ let(:td) { ActiveRecord::ConnectionAdapters::CrateAdapter::TableDefinition.new(nil, nil, nil, nil) }
9
+
10
+ it 'should simply set the key and values' do
11
+ s = {street: :string, city: :string}
12
+ str = td.send(:object_schema_to_string, s)
13
+ str.should eq "street string, city string"
14
+ end
15
+
16
+ it 'should simply properly parse an array definition' do
17
+ s = {street: :string, city: :string, phones: {array: :string}}
18
+ str = td.send(:object_schema_to_string, s)
19
+ str.should eq "street string, city string, phones array(string)"
20
+ end
21
+
22
+ end
23
+
24
+
25
+ end
26
+
@@ -0,0 +1,49 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe "Post#array" do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Migration.class_eval do
7
+ create_table :posts do |t|
8
+ t.string :title
9
+ t.integer :comment_count
10
+ t.array :tags, array_type: :string
11
+ t.array :votes, array_type: :integer
12
+ t.array :bool_arr, array_type: :boolean
13
+ end
14
+ end
15
+ Post.reset_column_information
16
+ end
17
+
18
+ after(:all) do
19
+ ActiveRecord::Migration.class_eval do
20
+ drop_table :posts
21
+ end
22
+ end
23
+
24
+
25
+ describe "#array column type" do
26
+ let(:array) {%w(hot fresh)}
27
+ let(:votes) {[9,8,7]}
28
+ let(:bool_arr) {[true, false, true]}
29
+ let(:post) {Post.create!(title: 'Arrays are awesome', tags: array, votes: votes, bool_arr: bool_arr )}
30
+
31
+ it 'should store and return an array' do
32
+ p = Post.find(post.id)
33
+ p.tags.should be_a Array
34
+ p.votes.should be_a Array
35
+ p.bool_arr.should be_a Array
36
+ p.tags.should eq array
37
+ p.votes.should eq votes
38
+ p.bool_arr.should eq bool_arr
39
+ end
40
+
41
+ it 'should find the post by array value' do
42
+ post = Post.create!(title: 'Arrays are awesome', tags: array, votes: votes)
43
+ refresh_posts
44
+ Post.where("'fresh' = ANY (tags)").should include(post)
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe "User#object" do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Migration.class_eval do
7
+ create_table :users do |t|
8
+ t.string :name
9
+ t.object :address, object_schema_behaviour: :strict,
10
+ object_schema: {street: :string, city: :string, phones: {array: :string}, zip: :integer}
11
+ end
12
+ end
13
+ User.reset_column_information
14
+ end
15
+
16
+ after(:all) do
17
+ ActiveRecord::Migration.class_eval do
18
+ drop_table :users
19
+ end
20
+ end
21
+
22
+
23
+ describe "#object column type" do
24
+ let(:address) {Address.new(street: '1010 W 2nd Ave', city: 'Vancouver', phones: ["123", "987"], zip: 6888)}
25
+ let(:user) {@user = User.create!(name: 'Mad Max', address: address)}
26
+
27
+ it 'should store and return an object' do
28
+ p = User.find(user.id)
29
+ p.address.should be_a Address
30
+ p.address.street.should eq address.street
31
+ p.address.city.should eq address.city
32
+ p.address.zip.should eq address.zip # without a object schema numbers are converted to strings
33
+ p.address.phones.should eq address.phones
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_record/attribute_methods/crate_object'
2
+
3
+ class Address
4
+ attr_accessor :street, :city, :phones, :zip
5
+
6
+ include CrateObject
7
+
8
+ def initialize(opts)
9
+ @street = opts[:street]
10
+ @city = opts[:city]
11
+ @phones = opts[:phones]
12
+ @zip = opts[:zip]
13
+ end
14
+
15
+ end
@@ -0,0 +1,9 @@
1
+ class Post < ActiveRecord::Base
2
+ before_create :set_id
3
+
4
+ private
5
+
6
+ def set_id
7
+ self.id = SecureRandom.uuid
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class User < ActiveRecord::Base
2
+ before_create :set_id
3
+
4
+ serialize :address, Address
5
+
6
+ private
7
+
8
+ def set_id
9
+ self.id = SecureRandom.uuid
10
+ end
11
+ end
@@ -0,0 +1,81 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Post do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Migration.class_eval do
7
+ create_table :posts do |t|
8
+ t.string :title
9
+ t.integer :views
10
+ end
11
+ end
12
+ Post.reset_column_information
13
+ end
14
+
15
+ after(:all) do
16
+ ActiveRecord::Migration.class_eval do
17
+ drop_table :posts
18
+ end
19
+ end
20
+
21
+ let(:params) { {title: "Crate rocks", views: 10000} }
22
+
23
+ context 'initialization' do
24
+ it 'should initialize a post object with all columns' do
25
+ post = Post.new(params)
26
+ post.should be_a(Post)
27
+ post.title.should eq "Crate rocks"
28
+ post.views.should eq 10000
29
+ end
30
+ end
31
+
32
+ context 'persistance' do
33
+
34
+ it 'should persist the record to the database' do
35
+ post = Post.create!(params)
36
+ post.persisted?.should be_true
37
+ refresh_posts
38
+ Post.count.should eq 1
39
+ end
40
+
41
+ end
42
+
43
+ context 'deletion' do
44
+
45
+ before do
46
+ @post = Post.create!(params)
47
+ end
48
+
49
+ it 'should destroy the record to the database' do
50
+ @post.destroy
51
+ Post.where(id: @post.id).should be_empty
52
+ end
53
+ end
54
+
55
+ describe 'existing record manipulation' do
56
+ before do
57
+ @post = Post.create!(params)
58
+ end
59
+
60
+ after do
61
+ #@post.destroy
62
+ end
63
+
64
+ context 'find' do
65
+ it 'should find the crated record' do
66
+ post = Post.where(id: @post.id).first
67
+ post.id.should eq(@post.id)
68
+ end
69
+ end
70
+
71
+ context 'update' do
72
+ it 'should update the record' do
73
+ @post.update_attributes(title: 'Crate Dope')
74
+ @post.reload.title.should eq('Crate Dope')
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ end
81
+
@@ -0,0 +1,43 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+ require 'activerecord-crate-adapter'
4
+ require 'logger'
5
+ #require 'debugger'
6
+
7
+ require 'dummy/app/models/address'
8
+ require 'dummy/app/models/post'
9
+ require 'dummy/app/models/user'
10
+
11
+ RSpec.configure do |config|
12
+
13
+ config.before(:each) do
14
+ end
15
+ config.after(:each) do
16
+ end
17
+ config.before(:suite) do
18
+ connect
19
+ end
20
+ config.after(:all) do
21
+ end
22
+ end
23
+
24
+ def connect
25
+ ActiveRecord::Base.logger = Logger.new("log/debug.log")
26
+ ActiveRecord::Base.logger.level = Logger::DEBUG
27
+ ActiveRecord::Base.configurations = {
28
+ 'arunit' => {
29
+ adapter: 'crate',
30
+ min_messages: 'warning',
31
+ port: 4209,
32
+ host: '127.0.0.1'
33
+ }
34
+ }
35
+ ActiveRecord::Base.establish_connection :arunit
36
+ end
37
+
38
+ # Crate is eventually consistent therefore we need
39
+ # to refresh the table when doing queries, except we
40
+ # query for the primary key
41
+ def refresh_posts
42
+ Post.connection.raw_connection.refresh_table('posts')
43
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'net/http'
4
+ class TestServer
5
+ CRATE_PATH = "~/crate"
6
+ TEST_PORT = 4209
7
+ NAME = "TestCluster"
8
+
9
+
10
+ def initialize(crate_home = nil, port = nil, host = "127.0.0.1")
11
+ @crate_home = crate_home || CRATE_PATH
12
+ @port = port || TEST_PORT
13
+ @host = host
14
+ end
15
+
16
+ def start
17
+ cmd = "sh #{CRATE_PATH}/bin/crate #{start_params}"
18
+ @pid = spawn(cmd, :out => "/tmp/crate_test_server.out", :err => "/tmp/crate_test_server.err")
19
+ Process.detach(@pid)
20
+ puts 'starting'
21
+ time_slept = 0
22
+ while true
23
+ puts "Crate not yet fully available. Waiting since #{time_slept} seconds..." unless alive?
24
+ sleep(2)
25
+ time_slept += 2
26
+ end
27
+ end
28
+
29
+ def stop
30
+ Process.kill("HUP", @pid)
31
+ end
32
+
33
+ private
34
+
35
+
36
+ def crate_exec
37
+ end
38
+
39
+ def crate_config
40
+ end
41
+
42
+ def start_params
43
+ "-Des.index.storage.type=memory " +
44
+ "-Des.node.name=#{NAME} " +
45
+ "-Des.cluster.name=Testing#{@port} " +
46
+ "-Des.http.port=#{@port}-#{@port} " +
47
+ "-Des.network.host=localhost " +
48
+ "-Des.discovery.type=zen " +
49
+ "-Des.discovery.zen.ping.multicast.enabled=false"
50
+ end
51
+
52
+ def alive?
53
+ req = Net::HTTP::Get.new('/')
54
+ resp = Net::HTTP.new(@host, @port)
55
+ begin
56
+ response = resp.start { |http| http.request(req) }
57
+ response.code == "200" ? true : false
58
+ rescue Errno::ECONNREFUSED
59
+ false
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ server = TestServer.new.start *ARGV
66
+
67
+ trap("INT") do
68
+ puts "Script terminated by user."
69
+ server.stop
70
+ puts "Server stopped"
71
+ exit
72
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-crate-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Christoph Klocker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
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
+ - !ruby/object:Gem::Dependency
42
+ name: debugger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 4.0.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 4.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: arel
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 4.0.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 4.0.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: crate_ruby
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.0.5
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.0.5
111
+ description: ActiveRecord adapter for Crate, a shared-nothing, fully searchable, document-oriented
112
+ cluster data store.
113
+ email:
114
+ - christoph@vedanova.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - activerecord-crate-adapter.gemspec
125
+ - history.txt
126
+ - lib/active_record/attribute_methods/crate_object.rb
127
+ - lib/active_record/connection_adapters/crate/database_statements.rb
128
+ - lib/active_record/connection_adapters/crate/quoting.rb
129
+ - lib/active_record/connection_adapters/crate/schema_statements.rb
130
+ - lib/active_record/connection_adapters/crate_adapter.rb
131
+ - lib/activerecord-crate-adapter.rb
132
+ - lib/activerecord-crate-adapter/version.rb
133
+ - lib/arel/arel_crate.rb
134
+ - lib/arel/visitors/crate.rb
135
+ - log/.keep
136
+ - spec/activerecord/connection_adapters/crate/crate_adapter_spec.rb
137
+ - spec/activerecord/connection_adapters/crate/table_definition_spec.rb
138
+ - spec/data_types/array_spec.rb
139
+ - spec/data_types/object_spec.rb
140
+ - spec/dummy/app/models/address.rb
141
+ - spec/dummy/app/models/post.rb
142
+ - spec/dummy/app/models/user.rb
143
+ - spec/models/post_spec.rb
144
+ - spec/spec_helper.rb
145
+ - spec/test_server.rb
146
+ homepage: https://crate.io
147
+ licenses:
148
+ - MIT
149
+ metadata: {}
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 2.2.1
167
+ signing_key:
168
+ specification_version: 4
169
+ summary: ActiveRecord Crate Data Adapter
170
+ test_files:
171
+ - spec/activerecord/connection_adapters/crate/crate_adapter_spec.rb
172
+ - spec/activerecord/connection_adapters/crate/table_definition_spec.rb
173
+ - spec/data_types/array_spec.rb
174
+ - spec/data_types/object_spec.rb
175
+ - spec/dummy/app/models/address.rb
176
+ - spec/dummy/app/models/post.rb
177
+ - spec/dummy/app/models/user.rb
178
+ - spec/models/post_spec.rb
179
+ - spec/spec_helper.rb
180
+ - spec/test_server.rb
181
+ has_rdoc: