tinia 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format nested
3
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "aws_cloud_search"
7
+ gem "rails", "~> 2.3"
8
+
9
+
10
+ # Add dependencies to develop your gem here.
11
+ # Include everything needed to run rake, tests, features, etc.
12
+ group :development do
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "guard-rspec"
15
+ gem "jeweler", "~> 1.8.3"
16
+ gem "mocha"
17
+ gem "rdoc", "~> 3.12"
18
+ gem "rspec", "~> 2.8.0"
19
+ gem "ruby-debug19", :require => "ruby-debug"
20
+ gem "simplecov"
21
+ gem "sqlite3"
22
+ gem "yard", "~> 0.7"
23
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,95 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (2.3.14)
5
+ actionpack (= 2.3.14)
6
+ actionpack (2.3.14)
7
+ activesupport (= 2.3.14)
8
+ rack (~> 1.1.0)
9
+ activerecord (2.3.14)
10
+ activesupport (= 2.3.14)
11
+ activeresource (2.3.14)
12
+ activesupport (= 2.3.14)
13
+ activesupport (2.3.14)
14
+ archive-tar-minitar (0.5.2)
15
+ aws_cloud_search (0.0.1)
16
+ faraday_middleware (>= 0.8.0)
17
+ columnize (0.3.6)
18
+ diff-lcs (1.1.3)
19
+ faraday (0.8.0)
20
+ multipart-post (~> 1.1)
21
+ faraday_middleware (0.8.7)
22
+ faraday (>= 0.7.4, < 0.9)
23
+ ffi (1.0.11)
24
+ git (1.2.5)
25
+ guard (1.0.1)
26
+ ffi (>= 0.5.0)
27
+ thor (~> 0.14.6)
28
+ guard-rspec (0.7.0)
29
+ guard (>= 0.10.0)
30
+ jeweler (1.8.3)
31
+ bundler (~> 1.0)
32
+ git (>= 1.2.5)
33
+ rake
34
+ rdoc
35
+ json (1.6.6)
36
+ linecache19 (0.5.12)
37
+ ruby_core_source (>= 0.1.4)
38
+ metaclass (0.0.1)
39
+ mocha (0.10.5)
40
+ metaclass (~> 0.0.1)
41
+ multi_json (1.3.2)
42
+ multipart-post (1.1.5)
43
+ rack (1.1.3)
44
+ rails (2.3.14)
45
+ actionmailer (= 2.3.14)
46
+ actionpack (= 2.3.14)
47
+ activerecord (= 2.3.14)
48
+ activeresource (= 2.3.14)
49
+ activesupport (= 2.3.14)
50
+ rake (>= 0.8.3)
51
+ rake (0.9.2.2)
52
+ rdoc (3.12)
53
+ json (~> 1.4)
54
+ rspec (2.8.0)
55
+ rspec-core (~> 2.8.0)
56
+ rspec-expectations (~> 2.8.0)
57
+ rspec-mocks (~> 2.8.0)
58
+ rspec-core (2.8.0)
59
+ rspec-expectations (2.8.0)
60
+ diff-lcs (~> 1.1.2)
61
+ rspec-mocks (2.8.0)
62
+ ruby-debug-base19 (0.11.25)
63
+ columnize (>= 0.3.1)
64
+ linecache19 (>= 0.5.11)
65
+ ruby_core_source (>= 0.1.4)
66
+ ruby-debug19 (0.11.6)
67
+ columnize (>= 0.3.1)
68
+ linecache19 (>= 0.5.11)
69
+ ruby-debug-base19 (>= 0.11.19)
70
+ ruby_core_source (0.1.5)
71
+ archive-tar-minitar (>= 0.5.2)
72
+ simplecov (0.6.2)
73
+ multi_json (~> 1.3)
74
+ simplecov-html (~> 0.5.3)
75
+ simplecov-html (0.5.3)
76
+ sqlite3 (1.3.5)
77
+ thor (0.14.6)
78
+ yard (0.7.5)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ aws_cloud_search
85
+ bundler (~> 1.0.0)
86
+ guard-rspec
87
+ jeweler (~> 1.8.3)
88
+ mocha
89
+ rails (~> 2.3)
90
+ rdoc (~> 3.12)
91
+ rspec (~> 2.8.0)
92
+ ruby-debug19
93
+ simplecov
94
+ sqlite3
95
+ yard (~> 0.7)
data/Guardfile ADDED
@@ -0,0 +1,19 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2, :opts => "--format nested" do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
+ watch('config/routes.rb') { "spec/routing" }
15
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
+ # Capybara request specs
17
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
18
+ end
19
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Dan Langevin
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,130 @@
1
+ = Tinia
2
+
3
+ A Rails interface for Amazon CloudSearch
4
+
5
+ Tinia allows you to easily index and search data using Amazon's hosted Solr service, CloudSearch. Its basic
6
+ workflow is that after_save we send data to CloudSearch and after_destroy we remove that data.
7
+
8
+ == Installing tinia
9
+
10
+ In Rails 3
11
+
12
+ # Gemfile
13
+ gem "tinia"
14
+
15
+ In Rails 2
16
+
17
+ # config/environment.rb
18
+ config.gem "tinia"
19
+
20
+ # config/initializers/nimbus.rb
21
+ Tinia.activate_active_record!
22
+
23
+ == Activating the Model
24
+
25
+ In order to activate a given model, you call the indexed_with_tinia class method. You must also supply a
26
+ tinia_domain. The tinia_domain is inheritable, so subclasses will use the same tinia_domain
27
+ as their parent.
28
+
29
+ class User < ActiveRecord::Base
30
+ indexed_with_cloud_search do |config|
31
+ config.cloud_search_domain = "users-xkdkdksl485d8d0d"
32
+ end
33
+ end
34
+
35
+ == Indexing data
36
+
37
+ In order to define the fields that you want indexed, you must define a cloud_search_data method, which returns a hash
38
+ of indexed data. You can call any necessary methods to generate this data.
39
+
40
+ class User < ActiveRecord::Base
41
+
42
+ indexed_with_cloud_search do |config|
43
+ config.cloud_search_domain = "users-xkdkdksl485d8d0d"
44
+ end
45
+
46
+ def cloud_search_data
47
+ {
48
+ "name" => self.name,
49
+ "age" => self.age,
50
+ "fathers_name" => self.father.name,
51
+ "mothers_name" => self.mother.name
52
+ }
53
+ end
54
+
55
+ end
56
+
57
+ Each time a record is saved, the index for that record will be updated. When the record is destroyed, it is removed
58
+ from the index.
59
+
60
+ == Indexing a collection
61
+
62
+ When installing tinia, you will need to build your initial index. The index is built by finding each record and
63
+ adding it to a batched uploader. This is done for efficiency (but large collections will still be kind of slow).
64
+
65
+ The re-indexer uses find_each and takes the same arguments as that method.
66
+
67
+ # Eager load associated data
68
+ Client.cloud_search_reindex(:include => [:mother, :father])
69
+
70
+ == Searching
71
+
72
+ Searching is implemented as a named_scope with extensions to hold meta data about the result set
73
+
74
+ res = Client.cloud_search("Dan") # => will search our collection for records with a type of Client that matches the query "Dan"
75
+
76
+ Client.cloud_search("Dan").recent # => applies the 'recent' scope to our conditions generated by a result from CloudSearch
77
+
78
+ === Pagination
79
+
80
+ CloudSearch/Solr offers built-in pagination, which Tinia provides access to. For example
81
+
82
+ # default options
83
+ res = User.cloud_search("Dan")
84
+ res.current_page # => 1
85
+ res.per_page # => 20
86
+ res.offset # => 0
87
+ res.total_pages # => 5
88
+ res.total_entries # => 89
89
+
90
+ # with additional options
91
+ res = User.cloud_search("Dan", :page => 2, :per_page => 30)
92
+ res.current_page # => 2
93
+ res.per_page # => 30
94
+ res.offset # => 30
95
+ res.total_pages # => 3
96
+ res.total_entries # => 89
97
+
98
+ === WillPaginate
99
+
100
+ Tinia offers drop-in integration with will_paginate. Results of any Tinia search will work with the will_paginate
101
+ view helpers
102
+
103
+ # app/controllers/users_controller.rb
104
+ class UsersController < ApplicationController
105
+ # GET /users
106
+ def list
107
+ @users = User.cloud_search(params[:q])
108
+ end
109
+ end
110
+
111
+ # app/views/users/list.html
112
+ = will_paginate(@users)
113
+
114
+
115
+
116
+ == Contributing to Tinia
117
+
118
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
119
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
120
+ * Fork the project.
121
+ * Start a feature/bugfix branch.
122
+ * Commit and push until you are happy with your contribution.
123
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
124
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
125
+
126
+ == Copyright
127
+
128
+ Copyright (c) 2012 Dan Langevin and Lifebooker. See LICENSE.txt for
129
+ further details.
130
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "tinia"
18
+ gem.homepage = "http://github.com/dlangevin/tinia"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Rails integration for CloudSearch}
21
+ gem.description = %Q{Rails integration for CloudSearch}
22
+ gem.email = "dan.langevin@lifebooker.com"
23
+ gem.authors = ["Dan Langevin"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
data/lib/tinia.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'aws_cloud_search'
2
+ require 'tinia/connection'
3
+ require 'tinia/exceptions'
4
+ require 'tinia/index'
5
+ require 'tinia/search'
6
+
7
+ if defined?(Rails)
8
+ require 'tinia/railtie'
9
+ end
10
+
11
+ module Tinia
12
+
13
+ def self.connection(domain = "default")
14
+ @connections ||= {}
15
+ @connections[domain] ||= AWSCloudSearch::CloudSearch.new(domain)
16
+ end
17
+
18
+ # activate for ActiveRecord
19
+ def self.activate_active_record!
20
+ ::ActiveRecord::Base.send(:extend, Tinia::ActiveRecord)
21
+ end
22
+
23
+ module ActiveRecord
24
+
25
+ # activation method for an AR class
26
+ def indexed_with_cloud_search(&block)
27
+ mods = [
28
+ Tinia::Connection,
29
+ Tinia::Index,
30
+ Tinia::Search
31
+ ]
32
+ mods.each do |mod|
33
+ unless self.included_modules.include?(mod)
34
+ self.send(:include, mod)
35
+ end
36
+ end
37
+ # config block
38
+ yield(self) if block_given?
39
+
40
+ # ensure config is all set
41
+ unless self.cloud_search_domain.present?
42
+ raise Tinia::MissingSearchDomain.new(self)
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,24 @@
1
+ module Tinia
2
+ module Connection
3
+
4
+ def self.included(klass)
5
+ klass.send(:extend, ClassMethods)
6
+ klass.class_eval do
7
+ class_inheritable_accessor :cloud_search_domain
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # accessor for the cloud search connection
14
+ def cloud_search_connection
15
+ @cloud_search_connection ||= begin
16
+ Tinia.connection(
17
+ self.cloud_search_domain
18
+ )
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ module Tinia
2
+ class MissingSearchDomain < Exception
3
+ # constructor
4
+ def initialize(klass)
5
+ super("You must define a cloud_search_domain for #{klass}")
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,112 @@
1
+ module Tinia
2
+ module Index
3
+
4
+ def self.included(klass)
5
+ klass.send(:include, InstanceMethods)
6
+ klass.send(:extend, ClassMethods)
7
+ klass.class_eval do
8
+ cattr_accessor :in_cloud_search_batch_documents
9
+ self.in_cloud_search_batch_documents = false
10
+
11
+ # set up our callbacks
12
+ after_save(:add_to_cloud_search)
13
+ after_destroy(:delete_from_cloud_search)
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ # add ourself as a document to CloudSearch
20
+ def add_to_cloud_search
21
+ self.class.cloud_search_add_document(
22
+ self.cloud_search_document
23
+ )
24
+ end
25
+
26
+ # empty implementation - re-implement
27
+ # or we might end up doing some meta-programming here
28
+ def cloud_search_data
29
+ {}
30
+ end
31
+
32
+ # wrapper for a fully formed AWSCloudSearch::Document
33
+ def cloud_search_document
34
+ AWSCloudSearch::Document.new.tap do |d|
35
+ d.id = self.id
36
+ d.lang = "en"
37
+ d.version = self.updated_at.to_i
38
+ # class name
39
+ d.add_field("type", self.class.base_class.name)
40
+ self.cloud_search_data.each_pair do |k,v|
41
+ d.add_field(k.to_s, v.to_s)
42
+ end
43
+ end
44
+ end
45
+
46
+ # add ourself as a document to CloudSearch
47
+ def delete_from_cloud_search
48
+ self.class.cloud_search_delete_document(
49
+ self.cloud_search_document
50
+ )
51
+ end
52
+
53
+ end
54
+
55
+ module ClassMethods
56
+
57
+ # class method to add documents
58
+ def cloud_search_add_document(doc)
59
+ self.cloud_search_batcher_command(:add_document, doc)
60
+ end
61
+
62
+ # class method to add documents
63
+ def cloud_search_delete_document(doc)
64
+ self.cloud_search_batcher_command(:delete_document, doc)
65
+ end
66
+
67
+ # perform all add/delete operations within a buffered
68
+ # DocumentBatcher
69
+ def cloud_search_batch_documents(&block)
70
+ begin
71
+ self.in_cloud_search_batch_documents = true
72
+ yield
73
+ # final flush for any left over documents
74
+ self.cloud_search_document_batcher.flush
75
+ ensure
76
+ self.in_cloud_search_batch_documents = false
77
+ end
78
+ end
79
+
80
+ # reindex the entire collection
81
+ def cloud_search_reindex(*args)
82
+ self.cloud_search_batch_documents do
83
+ self.find_each(*args) do |record|
84
+ record.add_to_cloud_search
85
+ end
86
+ end
87
+ end
88
+
89
+ # new instance of AWSCloudSearch::DocumentBatcher
90
+ def cloud_search_document_batcher
91
+ @cloud_search_document_batcher ||= begin
92
+ self.cloud_search_connection.new_batcher
93
+ end
94
+ end
95
+
96
+ protected
97
+ # send a command to the batcher and then conditionally flush
98
+ # depending on whether we are in a cloud_search_batch_documents
99
+ # block
100
+ def cloud_search_batcher_command(command, doc)
101
+ # send the command to our batcher
102
+ self.cloud_search_document_batcher.send(command, doc)
103
+
104
+ # if we are not in a batch_documents block, flush immediately
105
+ unless self.in_cloud_search_batch_documents
106
+ self.cloud_search_document_batcher.flush
107
+ end
108
+ end
109
+ end
110
+
111
+ end
112
+ end