dm-is-schemaless 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Doe
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,48 @@
1
+ = dm-is-schemaless
2
+
3
+ Inspired by http://bret.appspot.com/entry/how-friendfeed-uses-mysql I wanted to build a way to do that seamless. Hence this plugin.
4
+
5
+ = Basics
6
+
7
+ It's pretty straight forward. The bare minimum to use it is:
8
+
9
+ class Message
10
+ include DataMapper::Resource
11
+
12
+ is :schemaless
13
+
14
+ # The following properties will be defined automatically
15
+ # property :added_id, DataMapper::Types::Serial, :key => false
16
+ # property :id, DataMapper::Types::UUID, :unique => true, :nullable => false, :index => true
17
+ # property :updated, DataMapper::Types::EpochTime, :key => true, :index => true
18
+ # property :body, DataMapper::Types::Json
19
+ end
20
+
21
+ Away you go! By default it creates keys and a few other fields. It adds a bit of method missing magic so any property you want automatically has name, name=, and name?. You should use these instead of accessing the body hash directly in order to keep nil indexes from being setup.
22
+
23
+ = Indexes
24
+
25
+ Declaring indexes. Just use the class level index_on method and supply a symbol. This will create the association and a table called <property>Index. It also creates an update hook to monitor the record when its save and handle creating/updating/destroying the index record.
26
+
27
+ class Message
28
+ include DataMapper::Resource
29
+
30
+ is :schemaless
31
+
32
+ index_on :email
33
+ end
34
+
35
+ = Querying
36
+
37
+ This is handled for you automatically. After you create an index on a property whenever you use that in a query it will transform the query to look it up on the index table instead. So internally here's what happens.
38
+
39
+ # original query
40
+ Message.all(:email => 'test@gmail.com')
41
+ # transformed query
42
+ Message.all('email_index.email' => 'test@gmail.com')
43
+
44
+ This will also still support all of DM's query operators.
45
+
46
+ Props to Dan Kubb for all his awesome work on DM and helping fix/refine this code.
47
+
48
+ File all bugs as issues on the project http://github.com/BrianTheCoder/dm-is-schemaless
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "dm-is-schemaless"
8
+ gem.summary = %Q{An implementation of friendfeed's schemaless store for rbdms'}
9
+ gem.description = %Q{A plugin that allows you to easily treat an rdbms like a schemaless store, perfect for something like heroku *wink, wink*}
10
+ gem.email = "wbsmith83@gmail.com"
11
+ gem.homepage = "http://github.com/BrianTheCoder/dm-is-schemales"
12
+ gem.authors = ["brianthecoder"]
13
+ gem.files.include %w(lib/dm-is-schemaless.rb lib/dm-is-schemaless/is/index.rb lib/dm-is-schemaless/is/schemaless.rb lib/dm-is-schemaless/is/version.rb)
14
+ gem.add_development_dependency "yard"
15
+ gem.add_dependency "dm-types"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/*_test.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ begin
46
+ require 'reek/rake_task'
47
+ Reek::RakeTask.new do |t|
48
+ t.fail_on_error = true
49
+ t.verbose = false
50
+ t.source_files = 'lib/**/*.rb'
51
+ end
52
+ rescue LoadError
53
+ task :reek do
54
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
55
+ end
56
+ end
57
+
58
+ task :default => :test
59
+
60
+ begin
61
+ require 'yard'
62
+ YARD::Rake::YardocTask.new
63
+ rescue LoadError
64
+ task :yardoc do
65
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
66
+ end
67
+ end
data/TODO ADDED
File without changes
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.10.2
@@ -0,0 +1,143 @@
1
+ require 'rubygems'
2
+ require 'benchmark'
3
+ require 'extlib'
4
+ require 'randexp'
5
+ require 'dm-core'
6
+ require 'dm-types'
7
+
8
+ PROPS = 10.of{ /\w+/.gen }
9
+
10
+ class SimpleDataMapper
11
+ include DataMapper::Resource
12
+
13
+ property :id, Serial
14
+ property :body, DataMapper::Types::Json, :default => {}
15
+ end
16
+
17
+ class Missing < SimpleDataMapper
18
+ def method_missing(method_symbol, *args)
19
+ method_name = method_symbol.to_s
20
+ case method_name[-1..-1]
21
+ when "="
22
+ val = args.first
23
+ prop = method_name[0..-2]
24
+ if val.blank? && body.has_key?(prop)
25
+ body.delete(prop)
26
+ else
27
+ body[prop] = args.first
28
+ end
29
+ when "?"
30
+ body[method_name[0..-2]] == true
31
+ else
32
+ # Returns nil on failure so forms will work
33
+ body[method_name]
34
+ end
35
+ end
36
+ end
37
+
38
+ class DefineMethod < SimpleDataMapper
39
+ private
40
+
41
+ def method_missing(method_symbol, *args)
42
+ method_name = method_symbol.to_s
43
+
44
+ method = case method_name[-1, -1]
45
+ when '?' then define_bool(method_symbol, method_name[0..-2])
46
+ when '=' then define_setter(method_symbol, method_name[0..-2])
47
+ else define_getter(method_symbol)
48
+ end
49
+
50
+ method.call(*args)
51
+ end
52
+
53
+ def define_bool(method_symbol, property_name)
54
+ self.class.send(:define_method, method_symbol) do
55
+ body[property_name].blank?
56
+ end
57
+ end
58
+
59
+ def define_setter(method_symbol, property_name)
60
+ self.class.send(:define_method, method_symbol) do |value|
61
+ if value.blank?
62
+ body.delete(property_name)
63
+ else
64
+ body[property_name] = value
65
+ end
66
+ end
67
+ end
68
+
69
+ def define_getter(property_name)
70
+ self.class.send(:define_method, property_name) do
71
+ body[property_name]
72
+ end
73
+ end
74
+ end
75
+
76
+ class InstanceEvalMethod < SimpleDataMapper
77
+ private
78
+
79
+ def method_missing(method_symbol, *args)
80
+ method_name = method_symbol.to_s
81
+ case method_name[-1..-1]
82
+ when "="
83
+ define_setter(method_name, method_name[0..-2], args.first)
84
+ when "?"
85
+ define_bool(method_name, method_name[0..-2])
86
+ else
87
+ define_getter(method_name)
88
+ end
89
+ end
90
+
91
+ def define_getter(prop)
92
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
93
+ def #{prop}
94
+ body["#{prop}"]
95
+ end
96
+ RUBY
97
+ send(prop)
98
+ end
99
+
100
+ def define_setter(method, prop, value)
101
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
102
+ def #{method}(val)
103
+ if val.blank? && body.has_key?("#{prop}")
104
+ body.delete("#{prop}")
105
+ else
106
+ body["#{prop}"] = val
107
+ end
108
+ end
109
+ RUBY
110
+ send(method, value)
111
+ end
112
+
113
+ def define_bool(method, prop)
114
+ method_body = lambda{ body[prop].blank? }
115
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
116
+ def #{method}
117
+ body["#{prop}"].blank?
118
+ end
119
+ RUBY
120
+ send(method)
121
+ end
122
+ end
123
+
124
+ def bench(klass, n = 20000)
125
+ extensions = ['','=','?']
126
+ n.times do
127
+ model = klass.new
128
+ ext = extensions.pick
129
+ method = "#{PROPS.pick}#{ext}"
130
+ case ext
131
+ when '='
132
+ model.send(method, /\w+/.gen)
133
+ else
134
+ model.send(method)
135
+ end
136
+ end
137
+ end
138
+
139
+ Benchmark.bm(30) do |x|
140
+ x.report('method missing'){ bench(Missing) }
141
+ x.report('method missing w/define_method'){ bench(DefineMethod) }
142
+ x.report('method missing w/instance_eval'){ bench(InstanceEvalMethod) }
143
+ end
@@ -0,0 +1,22 @@
1
+ require 'benchmark'
2
+ require 'rubygems'
3
+ require 'dm-core'
4
+
5
+ def respond_to_bench(n = 100000)
6
+ fields = [ :test, :test.gt ]
7
+ n.times do
8
+ fields[rand(fields.size)].respond_to?(:target)
9
+ end
10
+ end
11
+
12
+ def is_a_bench(n = 100000)
13
+ fields = [ :test, :test.gt ]
14
+ n.times do
15
+ fields[rand(fields.size)].is_a?(DataMapper::Query::Operator)
16
+ end
17
+ end
18
+
19
+ Benchmark.bm(10) do |x|
20
+ x.report('respond_to?'){ respond_to_bench }
21
+ x.report('is_a?'){ is_a_bench }
22
+ end
@@ -0,0 +1,67 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dm-is-schemaless}
8
+ s.version = "0.10.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["brianthecoder"]
12
+ s.date = %q{2010-02-27}
13
+ s.description = %q{A plugin that allows you to easily treat an rdbms like a schemaless store, perfect for something like heroku *wink, wink*}
14
+ s.email = %q{wbsmith83@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc",
18
+ "TODO"
19
+ ]
20
+ s.files = [
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "TODO",
25
+ "VERSION",
26
+ "benchmarks/dynamic_methods.rb",
27
+ "benchmarks/operator_test.rb",
28
+ "dm-is-schemaless.gemspec",
29
+ "lib/dm-is-schemaless.rb",
30
+ "lib/dm-is-schemaless/is/index.rb",
31
+ "lib/dm-is-schemaless/is/schemaless.rb",
32
+ "lib/dm-is-schemaless/is/version.rb",
33
+ "spec/integration/schemaless_spec.rb",
34
+ "spec/models.rb",
35
+ "spec/spec.opts",
36
+ "spec/spec_helper.rb",
37
+ "tasks/install.rb",
38
+ "tasks/spec.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/BrianTheCoder/dm-is-schemales}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.5}
44
+ s.summary = %q{An implementation of friendfeed's schemaless store for rbdms'}
45
+ s.test_files = [
46
+ "spec/integration/schemaless_spec.rb",
47
+ "spec/models.rb",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_development_dependency(%q<yard>, [">= 0"])
57
+ s.add_runtime_dependency(%q<dm-types>, [">= 0"])
58
+ else
59
+ s.add_dependency(%q<yard>, [">= 0"])
60
+ s.add_dependency(%q<dm-types>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<yard>, [">= 0"])
64
+ s.add_dependency(%q<dm-types>, [">= 0"])
65
+ end
66
+ end
67
+
@@ -0,0 +1,8 @@
1
+ require 'dm-is-schemaless/is/schemaless'
2
+ require 'dm-is-schemaless/is/index'
3
+ require 'dm-types'
4
+ require 'guid'
5
+
6
+ module DataMapper::Model
7
+ include DataMapper::Is::Schemaless
8
+ end
@@ -0,0 +1,47 @@
1
+ module DataMapper
2
+ module Is
3
+ module Schemaless
4
+ class Index
5
+ attr_accessor :storage_name, :parent, :assoc_name, :model
6
+
7
+ class IndexingError < StandardError; end
8
+
9
+ def initialize(resource,field, opts)
10
+ name = "#{field.to_s.camel_case}Index"
11
+ @storage_name = Extlib::Inflection.tableize(name)
12
+ @parent = :"#{resource.to_s.snake_case}"
13
+ @model = build_resource(name, field, resource)
14
+ update_field_callbacks(resource, field)
15
+ end
16
+
17
+ def update_field_callbacks(model, field)
18
+ self.assoc_name = @model.to_s.snake_case
19
+ model.has 1, assoc_name.to_sym
20
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
21
+ def update_#{field}_index
22
+ if body.key?('#{field}')
23
+ self.#{assoc_name} ||= #{@model}.new
24
+ #{assoc_name}.#{field} = body['#{field}']
25
+ elsif #{assoc_name} && #{assoc_name}.destroy
26
+ self.#{assoc_name} = nil
27
+ else
28
+ end
29
+ end
30
+ RUBY
31
+ model.before :save, :"update_#{field}_index"
32
+ end
33
+
34
+ def build_resource(name, field, parent_resource)
35
+ klass = Object.const_set(name, Class.new)
36
+ klass.send(:include, DataMapper::Resource)
37
+ klass.property field.to_sym, String, :key => true
38
+ parent_resource.key.each do |prop|
39
+ klass.property :"#{@parent}_#{prop.name}", prop.type, :key => true
40
+ end
41
+ klass.belongs_to @parent, :parent_key => parent_resource.key.map{|k| k.name }
42
+ klass
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,123 @@
1
+ module DataMapper
2
+ module Is
3
+ module Schemaless
4
+ ##
5
+ # fired when your plugin gets included into Resource
6
+ #
7
+ def self.included(base)
8
+ end
9
+ ##
10
+ # Methods that should be included in DataMapper::Model.
11
+ # Normally this should just be your generator, so that the namespace
12
+ # does not get cluttered. ClassMethods and InstanceMethods gets added
13
+ # in the specific resources when you fire is :example
14
+ ##
15
+
16
+ def is_schemaless(options = {})
17
+ # Add class-methods
18
+ extend DataMapper::Is::Schemaless::ClassMethods
19
+ # Add instance-methods
20
+ include DataMapper::Is::Schemaless::InstanceMethods
21
+ class_inheritable_accessor(:indexes)
22
+ self.indexes ||= {}
23
+
24
+ property :added_id, DataMapper::Types::Serial, :key => false
25
+ property :type, DataMapper::Types::Discriminator
26
+ property :id, DataMapper::Types::UUID, :unique => true,
27
+ :required => true,
28
+ :index => true,
29
+ :default => Proc.new{ Guid.new.to_s }
30
+ property :updated, DataMapper::Types::EpochTime, :key => true,
31
+ :index => true,
32
+ :default => Proc.new{ Time.now }
33
+ property :body, DataMapper::Types::Json, :default => {}
34
+
35
+ # before :save, :update_indexes
36
+ end
37
+
38
+ module ClassMethods
39
+ def storage_name(repository_name = default_repository_name); 'entities' end
40
+
41
+ def index_on(field, opts = {})
42
+ indexes[field] = Index.new(self, field, opts)
43
+ end
44
+
45
+ def all(query = {})
46
+ super transform_query(query)
47
+ end
48
+
49
+ def first(query = {})
50
+ super transform_query(query)
51
+ end
52
+
53
+ def last(query = {})
54
+ super transform_query(query)
55
+ end
56
+
57
+ private
58
+
59
+ def transform_query(query)
60
+ find_indexes(query).each do |(field, value)|
61
+ name = field.respond_to?(:target) ? field.target : field
62
+ rewritten_field = "#{indexes[name].assoc_name}.#{name}"
63
+ rewritten_field << ".#{key.operator}" if field.respond_to?(:operator)
64
+ query[rewritten_field] = value
65
+ query.delete(key)
66
+ end
67
+ end
68
+
69
+ def find_indexes(query)
70
+ query.select do |key, value|
71
+ indexes.has_key?(key.respond_to?(:target) ? key.target : key)
72
+ end
73
+ end
74
+ end
75
+
76
+ module InstanceMethods
77
+ def initialize(args = {})
78
+ super({})
79
+ self.body = args
80
+ end
81
+
82
+ def method_missing(method_symbol, *args)
83
+ method_name = method_symbol.to_s
84
+ if %w(? =).include?(method_name[-1,1])
85
+ method = method_name[0..-2]
86
+ operator = method_name[-1,1]
87
+ if operator == '='
88
+ set_value(method, args.first)
89
+ elsif operator == '?'
90
+ !body[method].blank?
91
+ end
92
+ else
93
+ body[method_name]
94
+ end
95
+ end
96
+
97
+ def set_value(method, val)
98
+ if val.blank?
99
+ body.delete(method)
100
+ else
101
+ body[method] = val
102
+ end
103
+ end
104
+
105
+ def update_indexes
106
+ self.class.indexes.each do |field, index|
107
+ field = field.to_s
108
+ assoc = if send(index.assoc_name).blank?
109
+ self.send(:"#{index.assoc_name}=", index.model.new)
110
+ else
111
+ self.send(index.assoc_name)
112
+ end
113
+ if body.key?(field)
114
+ assoc[field] = body[field]
115
+ elsif assoc && assoc.destroy
116
+ self.send(:"#{index.assoc_name}=", nil)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end # Schemaless
122
+ end # Is
123
+ end # DataMapper
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module Is
3
+ module Schemaless
4
+ VERSION = '0.10.2'.freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
4
+ require 'pathname'
5
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
6
+
7
+ describe 'DataMapper::Is::Schemaless' do
8
+
9
+ before :each do
10
+ DataMapper.auto_migrate!
11
+ @message = Message.new
12
+ @photo = Photo.new
13
+ end
14
+
15
+ describe 'common table' do
16
+ it 'should set each models table to entities' do
17
+ Message.storage_name.should == "entities"
18
+ Photo.storage_name.should == "entities"
19
+ Photo.storage_name.should == Message.storage_name
20
+ end
21
+ end
22
+
23
+ describe 'structure' do
24
+ {
25
+ :added_id => DataMapper::Types::Serial,
26
+ :id => DataMapper::Types::UUID,
27
+ :updated => DataMapper::Types::EpochTime,
28
+ :body => DataMapper::Types::Json
29
+ }.each do |k, v|
30
+ it "has the property #{k}" do
31
+ Message.properties[k].should_not be_nil
32
+ end
33
+
34
+ it "has the property #{k} of type #{v}" do
35
+ Message.properties[k].type.should == v
36
+ end
37
+ end
38
+ end
39
+
40
+ describe 'index tables' do
41
+ it 'should have empty indexes if none are created' do
42
+ @photo.indexes.should be_empty
43
+ end
44
+
45
+ it 'should add the index to the list' do
46
+ Message.indexes.should have_key(:email)
47
+ @message.indexes.should have_key(:email)
48
+ end
49
+
50
+ it 'should create a table named ModelProperty' do
51
+ defined?(EmailIndex).should == "constant"
52
+ end
53
+
54
+ {
55
+ :email => String,
56
+ :message_updated => DataMapper::Types::EpochTime
57
+ }.each do |k, v|
58
+ it "has the property #{k}" do
59
+ EmailIndex.properties[k].should_not be_nil
60
+ end
61
+
62
+ it "has the property #{k} of type #{v}" do
63
+ EmailIndex.properties[k].type.should == v
64
+ end
65
+ end
66
+
67
+ it 'should define a has n relationship on the model' do
68
+ Message.relationships[:email_index].should_not be_nil
69
+ end
70
+
71
+ it 'should define a belongs_to relationship on the index table' do
72
+ EmailIndex.relationships[:message].should_not be_nil
73
+ end
74
+ end
75
+
76
+ describe 'model_type field' do
77
+ it 'adds it to date on save' do
78
+ @message.save
79
+ @msg = Message.first
80
+ @msg.body.should have_key("model_type")
81
+ @msg.model_type.should == "Message"
82
+ end
83
+ end
84
+
85
+ describe 'update the index' do
86
+ before :each do
87
+ @message.email = Faker::Internet.free_email
88
+ p 'save 1'
89
+ @message.save
90
+ @message.reload
91
+ end
92
+
93
+ it 'should create a new record on save' do
94
+ @message.reload
95
+ @message.email_index.should_not be_nil
96
+ end
97
+
98
+ it 'should destroy the index if the value becomes nil' do
99
+ @message.email = nil
100
+ p 'message'
101
+ p @message
102
+ @message.save
103
+ p 'saved'
104
+ @message.email_index.should be_nil
105
+ end
106
+
107
+ it 'should update the index when the value is changed' do
108
+ email = Faker::Internet.free_email
109
+ @message.email = email
110
+ p 'message'
111
+ p @message
112
+ @message.save
113
+ p 'saved'
114
+ @message.email_index.email.should == email
115
+ end
116
+ end
117
+
118
+ describe 'querying' do
119
+ it 'should look in the index tables if the property is indexed' do
120
+ email = Faker::Internet.free_email
121
+ @message.email = email
122
+ @message.save
123
+ queried = Message.first(:email => email)
124
+ queried.id.should == @message.id
125
+ end
126
+ end
127
+ end
128
+ end
data/spec/models.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'dm-sweatshop'
2
+ require 'faker'
3
+
4
+ class Message
5
+ include DataMapper::Resource
6
+
7
+ is :schemaless
8
+
9
+ index_on :email
10
+ end
11
+
12
+ class Photo
13
+ include DataMapper::Resource
14
+
15
+ is :schemaless
16
+ end
17
+
18
+ Message.fixture{{
19
+ :username => Faker::Internet.user_name,
20
+ :email => Faker::Internet.free_email,
21
+ :body => Faker::Lorem.paragraph(10),
22
+ :city => Faker::Address.city,
23
+ :us_state_abbr => Faker::Address.us_state_abbr,
24
+ :post_code => Faker::Address.zip_code
25
+ }}
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --loadby random
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'logger'
3
+
4
+ # use local dm-core if running from a typical dev checkout.
5
+ lib = File.join('..', '..', 'dm-core', 'lib')
6
+ $LOAD_PATH.unshift(lib) if File.directory?(lib)
7
+ require 'dm-core'
8
+ require 'faker'
9
+
10
+ # Support running specs with 'rake spec' and 'spec'
11
+ $LOAD_PATH.unshift('lib') unless $LOAD_PATH.include?('lib')
12
+
13
+ require 'dm-is-schemaless'
14
+ load File.join(File.dirname(__FILE__), 'models.rb')
15
+
16
+ def load_driver(name, default_uri)
17
+ return false if ENV['ADAPTER'] != name.to_s
18
+
19
+ begin
20
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
21
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
22
+ true
23
+ rescue LoadError => e
24
+ warn "Could not load do_#{name}: #{e}"
25
+ false
26
+ end
27
+ end
28
+
29
+ ENV['ADAPTER'] ||= 'postgres'
30
+
31
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
32
+ HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/schemaless_test')
33
+ HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/schemaless_test')
34
+
35
+ # DataObjects::Postgres.logger = Logger.new(STDOUT)
data/tasks/install.rb ADDED
@@ -0,0 +1,13 @@
1
+ def sudo_gem(cmd)
2
+ sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
3
+ end
4
+
5
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
6
+ task :install => [ :package ] do
7
+ sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
8
+ end
9
+
10
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
11
+ task :uninstall => [ :clobber ] do
12
+ sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -Ix"
13
+ end
data/tasks/spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ begin
2
+ require 'spec/rake/spectask'
3
+
4
+ task :default => [ :spec ]
5
+
6
+ desc 'Run specifications'
7
+ Spec::Rake::SpecTask.new(:spec) do |t|
8
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
9
+ t.libs << 'lib' << 'spec' # needed for CI rake spec task, duplicated in spec_helper
10
+
11
+ begin
12
+ require 'rcov'
13
+ t.rcov = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
14
+ t.rcov_opts << '--exclude' << 'spec'
15
+ t.rcov_opts << '--text-summary'
16
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
17
+ rescue LoadError
18
+ # rcov not installed
19
+ rescue SyntaxError
20
+ # rcov syntax invalid
21
+ end
22
+ end
23
+ rescue LoadError
24
+ # rspec not installed
25
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-is-schemaless
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.2
5
+ platform: ruby
6
+ authors:
7
+ - brianthecoder
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-27 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: yard
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: dm-types
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: A plugin that allows you to easily treat an rdbms like a schemaless store, perfect for something like heroku *wink, wink*
36
+ email: wbsmith83@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ - TODO
45
+ files:
46
+ - LICENSE
47
+ - README.rdoc
48
+ - Rakefile
49
+ - TODO
50
+ - VERSION
51
+ - benchmarks/dynamic_methods.rb
52
+ - benchmarks/operator_test.rb
53
+ - dm-is-schemaless.gemspec
54
+ - lib/dm-is-schemaless.rb
55
+ - lib/dm-is-schemaless/is/index.rb
56
+ - lib/dm-is-schemaless/is/schemaless.rb
57
+ - lib/dm-is-schemaless/is/version.rb
58
+ - spec/integration/schemaless_spec.rb
59
+ - spec/models.rb
60
+ - spec/spec.opts
61
+ - spec/spec_helper.rb
62
+ - tasks/install.rb
63
+ - tasks/spec.rb
64
+ has_rdoc: true
65
+ homepage: http://github.com/BrianTheCoder/dm-is-schemales
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --charset=UTF-8
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.5
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: An implementation of friendfeed's schemaless store for rbdms'
92
+ test_files:
93
+ - spec/integration/schemaless_spec.rb
94
+ - spec/models.rb
95
+ - spec/spec_helper.rb