oss_active_record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +32 -0
  4. data/lib/oss_active_record.rb +4 -0
  5. data/lib/oss_active_record/search_request.rb +62 -0
  6. data/lib/oss_active_record/searchable.rb +128 -0
  7. data/lib/oss_active_record/version.rb +3 -0
  8. data/lib/tasks/oss_active_record_tasks.rake +4 -0
  9. data/test/dummy/README.rdoc +28 -0
  10. data/test/dummy/Rakefile +6 -0
  11. data/test/dummy/app/assets/javascripts/application.js +13 -0
  12. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  13. data/test/dummy/app/controllers/application_controller.rb +5 -0
  14. data/test/dummy/app/helpers/application_helper.rb +2 -0
  15. data/test/dummy/app/models/article.rb +11 -0
  16. data/test/dummy/app/models/document.rb +55 -0
  17. data/test/dummy/app/models/invalid_article.rb +12 -0
  18. data/test/dummy/app/models/invalid_document.rb +20 -0
  19. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  20. data/test/dummy/bin/bundle +3 -0
  21. data/test/dummy/bin/rails +4 -0
  22. data/test/dummy/bin/rake +4 -0
  23. data/test/dummy/config.ru +4 -0
  24. data/test/dummy/config/application.rb +23 -0
  25. data/test/dummy/config/boot.rb +5 -0
  26. data/test/dummy/config/database.yml +25 -0
  27. data/test/dummy/config/environment.rb +5 -0
  28. data/test/dummy/config/environments/development.rb +29 -0
  29. data/test/dummy/config/environments/production.rb +80 -0
  30. data/test/dummy/config/environments/test.rb +36 -0
  31. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  33. data/test/dummy/config/initializers/inflections.rb +16 -0
  34. data/test/dummy/config/initializers/mime_types.rb +5 -0
  35. data/test/dummy/config/initializers/oss_active_record.rb +1 -0
  36. data/test/dummy/config/initializers/secret_token.rb +12 -0
  37. data/test/dummy/config/initializers/session_store.rb +3 -0
  38. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  39. data/test/dummy/config/locales/en.yml +23 -0
  40. data/test/dummy/config/routes.rb +56 -0
  41. data/test/dummy/db/development.sqlite3 +0 -0
  42. data/test/dummy/db/migrate/20130711150619_create_documents.rb +17 -0
  43. data/test/dummy/db/migrate/20130901161100_create_articles.rb +10 -0
  44. data/test/dummy/db/schema.rb +36 -0
  45. data/test/dummy/db/test.sqlite3 +0 -0
  46. data/test/dummy/log/development.log +152 -0
  47. data/test/dummy/log/test.log +6212 -0
  48. data/test/dummy/public/404.html +58 -0
  49. data/test/dummy/public/422.html +58 -0
  50. data/test/dummy/public/500.html +57 -0
  51. data/test/dummy/public/favicon.ico +0 -0
  52. data/test/dummy/test/fixtures/articles.yml +13 -0
  53. data/test/dummy/test/fixtures/documents.yml +25 -0
  54. data/test/dummy/test/models/article_test.rb +18 -0
  55. data/test/dummy/test/models/document_test.rb +19 -0
  56. data/test/oss_active_record_test.rb +7 -0
  57. data/test/searchable_test.rb +4 -0
  58. data/test/test_helper.rb +15 -0
  59. metadata +180 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c8d98241b53d0f1f9dc6ee4dcb51ea5fa60f4aca
4
+ data.tar.gz: 8284c8abaa036e7ee4c80b5ca3b808b540f15320
5
+ SHA512:
6
+ metadata.gz: 451d6b0de1db3f12699cba08c5fa1c4e5cdf16bc456c5cf35abb64172dfa327ac5e97ae22e4d4b339a095db22e6a9fd14bb5fd9dbd9ec0f7cdc2865ee6b96bf0
7
+ data.tar.gz: 2cdb7d185b3fdea92cb2b56fcda65241f5e092505a79fae9e8d9f0e5112f443d92ce260ce68dfc4331a7e2c49d9f379c8e3432c1ac11b99f34f1fcfcdf32b640
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Ori Pekelman
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/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'OssActiveRecord'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,4 @@
1
+ require "oss_active_record/searchable"
2
+ require "oss_active_record/search_request"
3
+ module OssActiveRecord
4
+ end
@@ -0,0 +1,62 @@
1
+ module OssActiveRecord
2
+ class SearchRequest
3
+ def initialize(index, text_fields, sortable_fields)
4
+ @index = index
5
+ @params = {'start' => 0, 'rows' => 10}
6
+ @filters = [];
7
+ @text_fields = text_fields;
8
+ @sortable_fields = sortable_fields;
9
+ @order_by = {};
10
+ end
11
+
12
+ def fulltext(keywords, &block)
13
+ @params['query'] = keywords
14
+ end
15
+
16
+ def filter(field, value, negative)
17
+ @filters << {"type"=> "QueryFilter", "negative"=> negative, "query"=> "#{field}:#{value}"}
18
+ end
19
+
20
+ def with(field, value)
21
+ filter field, value, false
22
+ end
23
+
24
+ def without(field, value)
25
+ filter field, value, true
26
+ end
27
+
28
+ def returns(fields)
29
+ @params['returnedFields'] = fields.uniq
30
+ end
31
+
32
+ def paginate(params)
33
+ rows = params[:per_page].to_i
34
+ page = params[:page].to_i
35
+ @params['start'] = (page - 1) * rows
36
+ @params['rows'] = rows
37
+ end
38
+
39
+ def order_by(field = nil, direction = :asc)
40
+ field = field == :score ? 'score' :@sortable_fields[field]
41
+ @order_by[field.to_s] = direction.to_s.upcase unless field.nil?
42
+ end
43
+
44
+ def execute(&block)
45
+ self.instance_eval(&block) unless block.nil?
46
+ @params['filters'] = @filters unless @filters.length == 0
47
+ fields = []
48
+ @text_fields.each do |key, value|
49
+ fields<<{ "field"=> value,"phrase"=> true,"boost"=> 1.0}
50
+ end
51
+ @params['searchFields'] = fields unless fields.length == 0
52
+ sorts = []
53
+ @order_by.each do |key, value|
54
+ sorts<<{ "field"=> key,"direction"=> value}
55
+ end
56
+ @params['sorts'] = sorts unless sorts.length == 0
57
+ return @index.search_field(@params)
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,128 @@
1
+ module OssActiveRecord
2
+ module Searchable
3
+ extend ActiveSupport::Concern
4
+ def index
5
+ doc = self.to_indexable
6
+ oss_doc = Oss::Document.new
7
+ doc.each do |name,value|
8
+ oss_doc.fields << Oss::Field.new(name, value)
9
+ end
10
+ self.class.oss_index.documents << oss_doc
11
+ self.class.oss_index.index!
12
+ end
13
+
14
+ module ClassMethods
15
+ @@field_types= [:integer, :text, :string, :time] #supported field types
16
+ @@_fields=[]
17
+ @@_field_id = nil
18
+ @@_text_fields = {}
19
+ @@_sortable_fields = {}
20
+ @@index = nil
21
+
22
+ def _fields
23
+ @@_fields
24
+ end
25
+
26
+ def searchable(options = {}, &block)
27
+ yield
28
+ unless options[:auto_index] == false
29
+ after_save :index
30
+ end
31
+ end
32
+
33
+ def oss_index
34
+ if @@index.nil?
35
+ @@index_name ||= self.name.downcase
36
+ @@index = Oss::Index.new(@@index_name, Rails.configuration.open_search_server_url)
37
+ create_schema!
38
+ end
39
+ @@index
40
+ end
41
+
42
+ def add_field(name, type, block=nil)
43
+ @@_fields<<{:name => name, :type => type,:block => block}
44
+ end
45
+
46
+ def create_schema!
47
+ @@index.create('EMPTY_INDEX') unless @@index.list.include? @@index_name
48
+ @@_field_id = @@_fields.detect {|f| f[:name] == :id }
49
+ @@_field_id = {:name => 'id', :type => 'integer',:block => nil} if @@_field_id.nil?
50
+ @@_fields << @@_field_id
51
+ @@_fields.each do |field|
52
+ create_schema_field!(field)
53
+ end
54
+ end
55
+
56
+ def reindex!
57
+ self.oss_index.delete!
58
+ self.create_schema!
59
+
60
+ self.all.find_in_batches do |group|
61
+ group.each { |doc| doc.index }
62
+ end
63
+ end
64
+
65
+ def create_schema_field!(field)
66
+ analyzers = { :text => 'StandardAnalyzer', :integer => 'DecimalAnalyzer'}
67
+ analyzer = analyzers[field[:type]] if field[:name] != :id
68
+ termVectors = { :text => 'POSITIONS_OFFSETS'}
69
+ termVector = termVectors[field[:type]] || 'NO'
70
+ name = "#{field[:name]}|#{field[:type]}"
71
+ params = {
72
+ 'name' => name,
73
+ 'analyzer' => analyzer,
74
+ 'stored' => 'NO',
75
+ 'indexed' => 'YES',
76
+ 'termVector' => termVector
77
+ }
78
+ @@_text_fields[field[:name]] = name if field[:type] == :text
79
+ @@_sortable_fields[field[:name]] = name unless field[:type] == :text
80
+ self.oss_index.set_field(params)
81
+ self.oss_index.set_field_default_unique(name, name) if field[:name] == :id
82
+ end
83
+
84
+ def method_missing(method, *args, &block)
85
+ yield unless block.nil?
86
+ add_field args[0], method, block if @@field_types.include? method
87
+ end
88
+
89
+ def search(*args, &block)
90
+ searchRequest = SearchRequest.new(self.oss_index, @@_text_fields, @@_sortable_fields)
91
+ searchRequest.returns @@_fields.map {|f|"#{f[:name]}|#{f[:type]}"}
92
+ active_record_from_result searchRequest.execute(&block)
93
+ end
94
+
95
+ def get_ids_from_results(search_result)
96
+ ids = []
97
+ id_field_name = "#{@@_field_id[:name]}|#{@@_field_id[:type]}"
98
+ search_result['documents'].each do |document|
99
+ document['fields'].each do |field|
100
+ ids << field['values'].map {|f|f.to_i}.uniq if field['fieldName'] == id_field_name
101
+ end
102
+ end
103
+ return ids
104
+ end
105
+
106
+ def active_record_from_result(search_result)
107
+ find get_ids_from_results(search_result)
108
+ end
109
+
110
+ end
111
+
112
+ def to_indexable
113
+ doc={}
114
+ self.class._fields.each do |field|
115
+ if field[:block].nil?
116
+ val = self.send(field[:name].to_sym)
117
+ else
118
+ val = field[:block].call
119
+ end
120
+ doc["#{field[:name]}|#{field[:type]}"]=val
121
+ end
122
+ doc
123
+ end
124
+
125
+ end
126
+ end
127
+
128
+ ActiveRecord::Base.send :include, OssActiveRecord::Searchable
@@ -0,0 +1,3 @@
1
+ module OssActiveRecord
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :oss_active_record do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Dummy::Application.load_tasks
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,11 @@
1
+ class Article < ActiveRecord::Base
2
+
3
+ searchable do
4
+ integer :id
5
+ text :title # fulltext
6
+ string :title # order_by
7
+ text :content #fulltext
8
+ time :updated_at
9
+ end
10
+
11
+ end
@@ -0,0 +1,55 @@
1
+ class CurrentRevision
2
+ def uuid
3
+ 42
4
+ end
5
+
6
+ def user_id
7
+ 42
8
+ end
9
+
10
+ def file_size
11
+ 42
12
+ end
13
+
14
+ def file_content_type
15
+ "42/42"
16
+ end
17
+
18
+ def state
19
+ "42"
20
+ end
21
+ end
22
+
23
+ class Folder
24
+ def room_id
25
+ 42
26
+ end
27
+ end
28
+
29
+ class Document < ActiveRecord::Base
30
+ # belongs_to :current_revision
31
+ def self.folder
32
+ Folder.new
33
+ end
34
+
35
+ def self.current_revision
36
+ CurrentRevision.new
37
+ end
38
+
39
+ searchable do
40
+ integer :id
41
+ integer :folder_id
42
+ integer :room_id do folder.room_id end
43
+
44
+ text :name # fulltext
45
+ string :name # order_by
46
+ time :updated_at
47
+
48
+ integer :uuid do current_revision.uuid end
49
+ integer :user_id do current_revision.user_id end
50
+ integer :file_size do current_revision.file_size end
51
+ string :file_content_type do current_revision.file_content_type end
52
+ string :state do current_revision.state end
53
+ end
54
+
55
+ end
@@ -0,0 +1,12 @@
1
+ class InvalidArticle < ActiveRecord::Base
2
+
3
+ searchable do
4
+ integer :id
5
+ glop :room_id
6
+ text :title # fulltext
7
+ string :title # order_by
8
+ text :content
9
+ time :updated_at
10
+ end
11
+
12
+ end
@@ -0,0 +1,20 @@
1
+ class InvalidDocument < ActiveRecord::Base
2
+ # belongs_to :current_revision
3
+
4
+ searchable do
5
+ integer :id
6
+ integer :folder_id
7
+ glop :room_id do folder.room_id end
8
+
9
+ plop :name # fulltext
10
+ string :name # order_by
11
+ time :updated_at
12
+
13
+ integer :uuid do current_revision.uuid end
14
+ integer :user_id do current_revision.user_id end
15
+ integer :file_size do current_revision.file_size end
16
+ string :file_content_type do current_revision.file_content_type end
17
+ string :state do current_revision.state end
18
+ end
19
+
20
+ end