activerecord-crate-adapter 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.
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: