friendly 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -1
- data/README.md +23 -0
- data/VERSION +1 -1
- data/friendly.gemspec +6 -2
- data/lib/friendly.rb +1 -0
- data/lib/friendly/indexer.rb +50 -0
- data/lib/friendly/storage_proxy.rb +8 -0
- data/lib/tasks/friendly.rake +7 -0
- data/spec/integration/offline_indexing_spec.rb +37 -0
- data/spec/unit/storage_proxy_spec.rb +26 -0
- metadata +6 -2
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
Changelog
|
2
2
|
=========
|
3
3
|
|
4
|
-
###
|
4
|
+
### 0.5
|
5
|
+
|
6
|
+
* (jamesgolick) Add offline index building.
|
7
|
+
|
8
|
+
### 0.4.4
|
5
9
|
|
6
10
|
* (jamesgolick) Make it possible to query with order, but no conditions.
|
7
11
|
* (jamesgolick) Add change tracking. This is mostly to facilitate arbitrary caches.
|
data/README.md
CHANGED
@@ -204,6 +204,29 @@ You can also use any other Friendly::Scope method like scope chaining.
|
|
204
204
|
|
205
205
|
See the section above or the Friendly::Scope docs for more details.
|
206
206
|
|
207
|
+
Offline Indexing
|
208
|
+
================
|
209
|
+
|
210
|
+
Friendly includes support for building an index in the background, without taking your app offline.
|
211
|
+
|
212
|
+
All you have to do is declare the index in your model. If we wanted to add an index on :name, :created_at in a User model, we'd do it like this:
|
213
|
+
|
214
|
+
class User
|
215
|
+
# ...snip...
|
216
|
+
|
217
|
+
indexes :name, :created_at
|
218
|
+
end
|
219
|
+
|
220
|
+
Then, make sure to run Friendly.create_tables! to create the table in the database. This won't overwrite any of your existing tables, so don't worry.
|
221
|
+
|
222
|
+
>> Friendly.create_tables!
|
223
|
+
|
224
|
+
Now that the the new table has been created, you need to copy the .rake file included with Friendly (lib/tasks/friendly.rake) to somewhere that will get picked up by your main Rakefile (lib/tasks if it's a rails project). Then, run:
|
225
|
+
|
226
|
+
$ KLASS=User FIELDS=name,created_at rake friendly:build_index
|
227
|
+
|
228
|
+
If you're running this in production, you'll probably want to fire up GNU screen so that it'll keep running even if you lose your SSH connection. When this task completes, the index is populated and ready to go!
|
229
|
+
|
207
230
|
Installation
|
208
231
|
============
|
209
232
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/friendly.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{friendly}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["James Golick"]
|
12
|
-
s.date = %q{2010-01-
|
12
|
+
s.date = %q{2010-01-25}
|
13
13
|
s.description = %q{}
|
14
14
|
s.email = %q{jamesgolick@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -47,6 +47,7 @@ Gem::Specification.new do |s|
|
|
47
47
|
"lib/friendly/document/storage.rb",
|
48
48
|
"lib/friendly/document_table.rb",
|
49
49
|
"lib/friendly/index.rb",
|
50
|
+
"lib/friendly/indexer.rb",
|
50
51
|
"lib/friendly/memcached.rb",
|
51
52
|
"lib/friendly/newrelic.rb",
|
52
53
|
"lib/friendly/query.rb",
|
@@ -61,6 +62,7 @@ Gem::Specification.new do |s|
|
|
61
62
|
"lib/friendly/time.rb",
|
62
63
|
"lib/friendly/translator.rb",
|
63
64
|
"lib/friendly/uuid.rb",
|
65
|
+
"lib/tasks/friendly.rake",
|
64
66
|
"rails/init.rb",
|
65
67
|
"spec/config.yml.example",
|
66
68
|
"spec/fakes/data_store_fake.rb",
|
@@ -81,6 +83,7 @@ Gem::Specification.new do |s|
|
|
81
83
|
"spec/integration/has_many_spec.rb",
|
82
84
|
"spec/integration/index_spec.rb",
|
83
85
|
"spec/integration/named_scope_spec.rb",
|
86
|
+
"spec/integration/offline_indexing_spec.rb",
|
84
87
|
"spec/integration/pagination_spec.rb",
|
85
88
|
"spec/integration/scope_chaining_spec.rb",
|
86
89
|
"spec/integration/table_creator_spec.rb",
|
@@ -174,6 +177,7 @@ Gem::Specification.new do |s|
|
|
174
177
|
"spec/integration/has_many_spec.rb",
|
175
178
|
"spec/integration/index_spec.rb",
|
176
179
|
"spec/integration/named_scope_spec.rb",
|
180
|
+
"spec/integration/offline_indexing_spec.rb",
|
177
181
|
"spec/integration/pagination_spec.rb",
|
178
182
|
"spec/integration/scope_chaining_spec.rb",
|
179
183
|
"spec/integration/table_creator_spec.rb",
|
data/lib/friendly.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Friendly
|
2
|
+
class Indexer
|
3
|
+
class << self
|
4
|
+
attr_accessor :objects_per_iteration
|
5
|
+
|
6
|
+
def populate(klass, *fields)
|
7
|
+
instance.populate(klass, klass.storage_proxy.index_for_fields(fields))
|
8
|
+
end
|
9
|
+
|
10
|
+
def instance
|
11
|
+
@instance ||= new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
self.objects_per_iteration = 100
|
16
|
+
|
17
|
+
attr_reader :datastore, :translator
|
18
|
+
|
19
|
+
def initialize(datastore = Friendly.datastore, translator = Translator.new)
|
20
|
+
@datastore = datastore
|
21
|
+
@translator = translator
|
22
|
+
end
|
23
|
+
|
24
|
+
def populate(klass, index)
|
25
|
+
loop do
|
26
|
+
count = 0
|
27
|
+
rows = datastore.all(klass, Query.new(:offset! => count,
|
28
|
+
:limit! => objects_per_iteration,
|
29
|
+
:order! => :added_id.asc))
|
30
|
+
rows.each do |attrs|
|
31
|
+
begin
|
32
|
+
index.create(translator.to_object(klass, attrs))
|
33
|
+
rescue Sequel::DatabaseError
|
34
|
+
# we can safely swallow this exception because if we've gotten
|
35
|
+
# to this point, we can be pretty sure that it's a duplicate
|
36
|
+
# key error, which just means that the object already exists
|
37
|
+
# in the index
|
38
|
+
end
|
39
|
+
end
|
40
|
+
break if rows.length < objects_per_iteration
|
41
|
+
count += objects_per_iteration
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def objects_per_iteration
|
47
|
+
self.class.objects_per_iteration
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -67,6 +67,14 @@ module Friendly
|
|
67
67
|
index
|
68
68
|
end
|
69
69
|
|
70
|
+
def index_for_fields(fields)
|
71
|
+
tables.detect do |t|
|
72
|
+
t.respond_to?(:fields) && t.fields == fields
|
73
|
+
end.tap do |i|
|
74
|
+
raise MissingIndex, "No index found matching #{fields.join(", ")}." if i.nil?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
70
78
|
protected
|
71
79
|
def each_store
|
72
80
|
stores.each { |s| yield(s) }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe "Building an index offline" do
|
4
|
+
before do
|
5
|
+
$db.drop_table :awesome_things if $db.table_exists?(:awesome_things)
|
6
|
+
|
7
|
+
if $db.table_exists?(:index_awesome_things_on_name)
|
8
|
+
$db.drop_table :index_awesome_things_on_name
|
9
|
+
end
|
10
|
+
|
11
|
+
@klass = Class.new do
|
12
|
+
def self.name; "AwesomeThing"; end
|
13
|
+
def self.table_name; "awesome_things"; end
|
14
|
+
|
15
|
+
include Friendly::Document
|
16
|
+
|
17
|
+
attribute :name, String
|
18
|
+
end
|
19
|
+
@klass.create_tables!
|
20
|
+
|
21
|
+
@jameses = [@klass.create(:name => "James"), @klass.create(:name => "James")]
|
22
|
+
|
23
|
+
@klass.indexes :name
|
24
|
+
@klass.create_tables!
|
25
|
+
Friendly::Indexer.populate(@klass, :name)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "builds the missing index rows for all the rows in the doc table" do
|
29
|
+
@klass.all(:name => "James").should == @jameses
|
30
|
+
end
|
31
|
+
|
32
|
+
it "ignores records that are already in the index" do
|
33
|
+
lambda {
|
34
|
+
Friendly::Indexer.populate(@klass, :name)
|
35
|
+
}.should_not raise_error
|
36
|
+
end
|
37
|
+
end
|
@@ -215,4 +215,30 @@ describe "Friendly::StorageProxy" do
|
|
215
215
|
@storage.count(:x => 1).should == 10
|
216
216
|
end
|
217
217
|
end
|
218
|
+
|
219
|
+
describe "getting the index that matches a set of fields" do
|
220
|
+
before do
|
221
|
+
@storage_factory = stub(:document_table => @document_table)
|
222
|
+
|
223
|
+
[[:name, :created_at], [:name]].each do |fields|
|
224
|
+
@storage_factory.stubs(:index).
|
225
|
+
with(@klass, *fields).returns(stub(:fields => fields))
|
226
|
+
end
|
227
|
+
|
228
|
+
@storage = Friendly::StorageProxy.new(@klass, @storage_factory)
|
229
|
+
@storage.add(:name, :created_at)
|
230
|
+
@storage.add(:name)
|
231
|
+
end
|
232
|
+
|
233
|
+
it "matches if and only if all the fields are present and in order" do
|
234
|
+
@storage.index_for_fields([:name, :created_at]).fields.should ==
|
235
|
+
[:name, :created_at]
|
236
|
+
end
|
237
|
+
|
238
|
+
it "raises missing index if there's no matching index" do
|
239
|
+
lambda {
|
240
|
+
@storage.index_for_fields([:created_at, :name])
|
241
|
+
}.should raise_error(Friendly::MissingIndex)
|
242
|
+
end
|
243
|
+
end
|
218
244
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: friendly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Golick
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-25 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- lib/friendly/document/storage.rb
|
133
133
|
- lib/friendly/document_table.rb
|
134
134
|
- lib/friendly/index.rb
|
135
|
+
- lib/friendly/indexer.rb
|
135
136
|
- lib/friendly/memcached.rb
|
136
137
|
- lib/friendly/newrelic.rb
|
137
138
|
- lib/friendly/query.rb
|
@@ -146,6 +147,7 @@ files:
|
|
146
147
|
- lib/friendly/time.rb
|
147
148
|
- lib/friendly/translator.rb
|
148
149
|
- lib/friendly/uuid.rb
|
150
|
+
- lib/tasks/friendly.rake
|
149
151
|
- rails/init.rb
|
150
152
|
- spec/config.yml.example
|
151
153
|
- spec/fakes/data_store_fake.rb
|
@@ -166,6 +168,7 @@ files:
|
|
166
168
|
- spec/integration/has_many_spec.rb
|
167
169
|
- spec/integration/index_spec.rb
|
168
170
|
- spec/integration/named_scope_spec.rb
|
171
|
+
- spec/integration/offline_indexing_spec.rb
|
169
172
|
- spec/integration/pagination_spec.rb
|
170
173
|
- spec/integration/scope_chaining_spec.rb
|
171
174
|
- spec/integration/table_creator_spec.rb
|
@@ -281,6 +284,7 @@ test_files:
|
|
281
284
|
- spec/integration/has_many_spec.rb
|
282
285
|
- spec/integration/index_spec.rb
|
283
286
|
- spec/integration/named_scope_spec.rb
|
287
|
+
- spec/integration/offline_indexing_spec.rb
|
284
288
|
- spec/integration/pagination_spec.rb
|
285
289
|
- spec/integration/scope_chaining_spec.rb
|
286
290
|
- spec/integration/table_creator_spec.rb
|