dm-is-schemaless 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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