active-asari 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ *.swp
20
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby '1.9.3'
4
+
5
+ gem 'asari', :git => 'git://github.com/lgleasain/asari.git'
6
+ gem 'aws-sdk'
7
+
8
+ group :test, :development do
9
+ gem 'rspec'
10
+ gem 'factory_girl'
11
+ gem 'jeweler'
12
+ gem 'simplecov'
13
+ gem 'activerecord', '~>3.2.13'
14
+ gem 'sqlite3'
15
+ end
@@ -0,0 +1,103 @@
1
+ GIT
2
+ remote: git://github.com/lgleasain/asari.git
3
+ revision: f17d8baf3707a36982566db5fc2f2fd792c2f023
4
+ specs:
5
+ asari (0.8.0)
6
+ httparty
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (3.2.13)
12
+ activesupport (= 3.2.13)
13
+ builder (~> 3.0.0)
14
+ activerecord (3.2.13)
15
+ activemodel (= 3.2.13)
16
+ activesupport (= 3.2.13)
17
+ arel (~> 3.0.2)
18
+ tzinfo (~> 0.3.29)
19
+ activesupport (3.2.13)
20
+ i18n (= 0.6.1)
21
+ multi_json (~> 1.0)
22
+ addressable (2.3.5)
23
+ arel (3.0.2)
24
+ aws-sdk (1.12.0)
25
+ json (~> 1.4)
26
+ nokogiri (< 1.6.0)
27
+ uuidtools (~> 2.1)
28
+ builder (3.0.4)
29
+ diff-lcs (1.2.4)
30
+ factory_girl (4.2.0)
31
+ activesupport (>= 3.0.0)
32
+ faraday (0.8.7)
33
+ multipart-post (~> 1.1)
34
+ git (1.2.5)
35
+ github_api (0.10.1)
36
+ addressable
37
+ faraday (~> 0.8.1)
38
+ hashie (>= 1.2)
39
+ multi_json (~> 1.4)
40
+ nokogiri (~> 1.5.2)
41
+ oauth2
42
+ hashie (2.0.5)
43
+ highline (1.6.19)
44
+ httparty (0.11.0)
45
+ multi_json (~> 1.0)
46
+ multi_xml (>= 0.5.2)
47
+ httpauth (0.2.0)
48
+ i18n (0.6.1)
49
+ jeweler (1.8.6)
50
+ builder
51
+ bundler (~> 1.0)
52
+ git (>= 1.2.5)
53
+ github_api (= 0.10.1)
54
+ highline (>= 1.6.15)
55
+ nokogiri (= 1.5.10)
56
+ rake
57
+ rdoc
58
+ json (1.8.0)
59
+ jwt (0.1.8)
60
+ multi_json (>= 1.5)
61
+ multi_json (1.7.7)
62
+ multi_xml (0.5.4)
63
+ multipart-post (1.2.0)
64
+ nokogiri (1.5.10)
65
+ oauth2 (0.9.2)
66
+ faraday (~> 0.8)
67
+ httpauth (~> 0.2)
68
+ jwt (~> 0.1.4)
69
+ multi_json (~> 1.0)
70
+ multi_xml (~> 0.5)
71
+ rack (~> 1.2)
72
+ rack (1.5.2)
73
+ rake (10.1.0)
74
+ rdoc (4.0.1)
75
+ json (~> 1.4)
76
+ rspec (2.14.1)
77
+ rspec-core (~> 2.14.0)
78
+ rspec-expectations (~> 2.14.0)
79
+ rspec-mocks (~> 2.14.0)
80
+ rspec-core (2.14.3)
81
+ rspec-expectations (2.14.0)
82
+ diff-lcs (>= 1.1.3, < 2.0)
83
+ rspec-mocks (2.14.1)
84
+ simplecov (0.7.1)
85
+ multi_json (~> 1.0)
86
+ simplecov-html (~> 0.7.1)
87
+ simplecov-html (0.7.1)
88
+ sqlite3 (1.3.7)
89
+ tzinfo (0.3.37)
90
+ uuidtools (2.1.4)
91
+
92
+ PLATFORMS
93
+ ruby
94
+
95
+ DEPENDENCIES
96
+ activerecord (~> 3.2.13)
97
+ asari!
98
+ aws-sdk
99
+ factory_girl
100
+ jeweler
101
+ rspec
102
+ simplecov
103
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 lgleasain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,102 @@
1
+ # ActiveAsari
2
+
3
+ ## Description
4
+
5
+ Full Active Record Integration with Asari along with migration-like indexing and domain creation. This makes using Amazon Cloud Search a lot easier in your rails application.
6
+
7
+ #### Why ActiveAsari?
8
+
9
+ Active Asari builds functionality on Asar which gives you a powerfull way to create and access Amazon Cloud Search instances and data.
10
+
11
+ ## Usage
12
+
13
+ #### Configuration
14
+
15
+ Include the gem in your project.
16
+
17
+ gem 'active_asari'
18
+
19
+ In your application.rb file or in the main part of your rack app. NOTE: The ActiveAsari.configure method needs a directory to be passed to it. It will look for the configuration files and load them from this location.
20
+
21
+ AWS.config({:access_key_id => 'your Amazon key id', :secret_access_key => 'your Amazon secret key'})
22
+ require 'active_asari'
23
+ ACTIVE_ASARI_CONFIG, ACTIVE_ASARI_ENV = ActiveAsari.configure(File.dirname(__FILE__))
24
+ require 'active_asari/active_record'
25
+
26
+ There are two configuration files that active_asari looks for. First, active_asari_env.yml. Right now this file is used to configure access permissions for your domains and the prefix to use for them. Prefixing was added so that you can have domains for multiple environments under one amazon account without having them stomp on each other. Here is an example of a active_asari_env.yml file.
27
+
28
+ development:
29
+ domain_prefix: dev
30
+ access_permissions:
31
+ - ip_address: 0.0.0.0/0
32
+ - ip_address: 25.44.23.25/32
33
+ staging:
34
+ domain_prefix: staging
35
+ access_permissions:
36
+ - ip_address: 192.168.66.23/32
37
+ - ip_address: 28.44.23.25/32
38
+
39
+ When you are in a test environment active_asari overlays are disabled. Right now the recommended way of testing asari calls is via mocking.
40
+
41
+ #### Your Domain
42
+
43
+ Domains are collections of data that you can store. ActiveAsari has taken the approach of associating a activerecord table with a domain. So if you have a talble called events then you might create a domain of the same name. Domain objects are specified in the active_asari_config.yml file. Below is an example of a file.
44
+
45
+ TestModel:
46
+ name:
47
+ index_field_type: text
48
+ search_enabled: true
49
+ amount:
50
+ index_field_type: uint
51
+ search_enabled: true
52
+ last_updated:
53
+ index_field_type: uint
54
+ search_enabled: false
55
+ bee_larvae_type:
56
+ index_field_type: literal
57
+ HoneyBadger:
58
+ name:
59
+ index_field_type: text
60
+
61
+ The domain is the top most element in the structure. The example above specifies two domains. The first one has indexes for name, amount, last_updated and bee_larvae_type. All active_asari fields are result enabled to allow you to retrive the result values from Amazon Cloud search. The index_field_type specifies the Amazon Cloud Search unit type. search_enabled specifies your ability to search on a field. text fields are always searchable, uint are configurable but default to searchable and literals need to be enabled if you want to be able to search them.
62
+
63
+ The ActiveAsari::Migrations class has methods to 'migrate' your cloud search instance to have domains with indexes specified in your config file. We have a todo to add rake tasks to the gem. In the meantime you can all migrate_all to create everything in your configuration file or migrate_domain to migrate one domain. Both of these commands also apply to access policies you specified in your env file. When the domain is created, the prefix you specified is added to the domain and it is uncased similar to rails but instead of using underscores it uses -. For example HoneyBadger in the development environment with a prefix of dev would create a domain called dev-honey-badger.
64
+
65
+ #### Your Models
66
+
67
+ Any model can be associated with a ActiveAsari Object. This is done by adding the following three lines to the beginning of your model class.
68
+
69
+ include ActiveAsari::ActiveRecord
70
+ include Asari::ActiveRecord if !env_test?
71
+ active_asari_index 'YourDomain'
72
+
73
+ You would substitute the appropriate domain specified in your config file. The !env_test option was the only way to not have ActiveAsari not interfere with tests. We would welcome pull requests to make this cleaner.
74
+
75
+ Once you have this in your model, and have run your migrations all update, create and deletes to the model in a non_test environment will automatically be performed in AmazonCloud search keeping the two environments in sync.
76
+
77
+ #### Searching
78
+
79
+ Searching is done via ActiveAsari.active_asari_search. IE:
80
+
81
+ ActiveAsari.active_asari_search 'HoneyBadger', 'name:beavis', :query_type => :boolean
82
+
83
+ The query_type allows you to specify if you want to do a boolean or regular query. All other options are passed directly to asari, so see the asari gem for documentation on how to use it. Results are automatically returned in a hash of objects indexed by the document_id. The object contains a raw_result accessor along with accessors for all returned fields in the hash. So...
84
+
85
+ my_result = ActiveAsari.active_asari_search 'HoneyBadger', 'beavis'
86
+ my_result['6'].name.should include 'beavis'
87
+
88
+ Search parameters are passed directly to Amazon Cloud Search. See it's documentation for otpions, syntax etc..
89
+
90
+
91
+ ## Contributions
92
+
93
+ If ActiveAsari interests you, please fork the repo and submit a pull request.
94
+
95
+ ### Contributors
96
+
97
+ * [Lance Gleason](https://github.com/lgleasain "lgleasain on Github")
98
+
99
+ ## License
100
+
101
+ MIT, see LICENSE for more details.
102
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "active-asari"
6
+ s.version = '0.0.2'
7
+ s.authors = ["Lance Gleason"]
8
+ s.email = ["lgleason@polyglotprogramminginc.com"]
9
+ s.homepage = "http://github.com/playon/active-asari"
10
+ s.summary = %q{ActiveAsari is a Ruby interface for AWS CloudSearch.}
11
+ s.description = %q{ActiveAsari is a Ruby interface for AWS CloudSearch. It uses Asari as a foundation and make it work more like Active Record.}
12
+
13
+ s.rubyforge_project = "asari"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+
24
+ s.add_runtime_dependency "asari"
25
+ s.add_runtime_dependency 'aws-sdk'
26
+
27
+ s.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,3 @@
1
+ database:
2
+ override:
3
+ - echo "skipping database setup"
Binary file
@@ -0,0 +1,7 @@
1
+ require 'asari'
2
+ require 'asari/active_record'
3
+ require 'active_asari/migrations'
4
+ require 'active_asari/active_record'
5
+ require 'active_asari/common'
6
+ require 'active_asari/result_object'
7
+
@@ -0,0 +1,21 @@
1
+ module ActiveAsari
2
+ module ActiveRecord
3
+
4
+ alias_attribute :active_asari_id, :id
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def env_test?
12
+ (ENV['RAILS_ENV'] == 'test' or ENV['RACK_ENV'] == 'test')
13
+ end
14
+
15
+ def active_asari_index(class_name)
16
+ active_asari_index_array = ACTIVE_ASARI_CONFIG[class_name].symbolize_keys.keys.concat [:active_asari_id]
17
+ asari_index ActiveAsari.asari_domain_name(class_name), active_asari_index_array if !env_test?
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveAsari
2
+
3
+ def self.amazon_safe_domain_name(domain)
4
+ environment = ENV['RAILS_ENV'] ? ENV['RAILS_ENV'] : ENV['RACK_ENV']
5
+ "#{ACTIVE_ASARI_ENV[environment]['domain_prefix']}-#{domain.underscore.sub(/_/, '-')}"
6
+ end
7
+
8
+ def self.aws_client
9
+ AWS::CloudSearch::Client.new
10
+ end
11
+
12
+ def self.asari_domain_name(domain)
13
+ $domains = {} if !$domains
14
+ amazon_domain = amazon_safe_domain_name domain
15
+ if !$domains[amazon_domain]
16
+ domain_data = aws_client.describe_domains[:domain_status_list].select { |domain_data|
17
+ domain_data[:domain_name] == amazon_domain}
18
+ $domains[amazon_domain] = domain_data.first[:search_service][:endpoint].split('.').first[7..-1]
19
+ end
20
+ $domains[amazon_domain]
21
+ end
22
+
23
+ def self.active_asari_raw_search(domain, query, search_options = {})
24
+ asari = Asari.new asari_domain_name(domain)
25
+ fields = ACTIVE_ASARI_CONFIG[domain].map {|field| field.first.to_sym}
26
+ fields = fields.concat([:active_asari_id])
27
+ search_options[:return_fields] = fields
28
+ asari.search query, search_options
29
+ end
30
+
31
+ def self.objectify_results(hash_results)
32
+ results = {}
33
+ hash_results.each do |key, value|
34
+ results[key] = ResultObject.new
35
+ results[key].raw_result = value
36
+ end
37
+ results
38
+ end
39
+
40
+ def self.active_asari_search(domain, query, search_options = {})
41
+ raw_result = active_asari_raw_search domain, query, search_options
42
+ objectify_results raw_result
43
+ end
44
+
45
+ def self.configure(yaml_file_dir)
46
+ active_asari_config = YAML.load_file(File.expand_path(yaml_file_dir) + '/active_asari_config.yml')
47
+ active_asari_env = YAML.load_file(File.expand_path(yaml_file_dir) + '/active_asari_env.yml')
48
+ return active_asari_config, active_asari_env
49
+ end
50
+ end
@@ -0,0 +1,61 @@
1
+ module ActiveAsari
2
+ class Migrations
3
+
4
+ attr_accessor :connection
5
+
6
+ def initialize
7
+ self.connection = AWS::CloudSearch::Client.new
8
+ end
9
+
10
+ def migrate_all
11
+ ACTIVE_ASARI_CONFIG.keys.each do |domain|
12
+ migrate_domain domain
13
+ end
14
+ end
15
+
16
+ def migrate_domain(domain)
17
+ create_domain domain
18
+ ACTIVE_ASARI_CONFIG[domain].each do |field|
19
+ create_index_field domain, field.first => field.last
20
+ end
21
+ create_index_field domain, 'active_asari_id' => {'index_field_type' => 'uint'}
22
+ connection.index_documents :domain_name => ActiveAsari.amazon_safe_domain_name(domain)
23
+ update_service_access_policies ActiveAsari.amazon_safe_domain_name(domain)
24
+ end
25
+
26
+ def update_service_access_policies(domain)
27
+ policy_array = []
28
+ asari_env = ENV['RAILS_ENV'] ? ENV['RAILS_ENV'] : ENV['RACK_ENV']
29
+ ACTIVE_ASARI_ENV[asari_env]['access_permissions'].each do |permission|
30
+ policy_array << {:Effect => :Allow, :Action => '*', :Resource => '*', :Condition => {:IpAddress => {'aws:SourceIp' => [permission['ip_address']]}}}
31
+ end
32
+ access_policies = {:Statement => policy_array}
33
+ connection.update_service_access_policies :domain_name => domain, :access_policies => access_policies.to_json
34
+ end
35
+
36
+ def create_index_field(domain, field)
37
+ index_field_name = field.keys.first
38
+ index_field_type = field[index_field_name]['index_field_type']
39
+ search_enabled = field[index_field_name]['search_enabled']
40
+ if search_enabled == nil or search_enabled.blank?
41
+ search_enabled = false
42
+ end
43
+
44
+
45
+ request = {:domain_name => ActiveAsari.amazon_safe_domain_name(domain), :index_field => {:index_field_name => index_field_name,
46
+ :index_field_type => index_field_type}}
47
+ case index_field_type
48
+ when 'literal'
49
+ request[:index_field][:literal_options] = {:search_enabled => search_enabled, :result_enabled => true}
50
+ when 'text'
51
+ request[:index_field][:text_options] = {:result_enabled => true}
52
+ end
53
+ connection.define_index_field request
54
+ end
55
+
56
+ def create_domain(domain)
57
+ connection.create_domain :domain_name => ActiveAsari.amazon_safe_domain_name(domain)
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ class ActiveAsari::ResultObject
2
+ attr_accessor :raw_result
3
+
4
+ def method_missing(method_sym, *arguments, &block)
5
+ base_method = method_sym.to_s.end_with?('_array') ? method_sym.to_s.chomp('_array') : method_sym.to_s
6
+ if raw_result.has_key? base_method
7
+ method_sym.to_s.end_with?('_array') ? raw_result[base_method] : raw_result[base_method].first
8
+ else
9
+ super
10
+ end
11
+ end
12
+
13
+ def respond_to?(method_sym, include_private = false)
14
+ base_method = method_sym.to_s.end_with?('_array') ? method_sym.to_s.chomp('_array') : method_sym.to_s
15
+ if raw_result.has_key? base_method
16
+ true
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ TestModel:
2
+ name:
3
+ index_field_type: text
4
+ search_enabled: true
5
+ amount:
6
+ index_field_type: uint
7
+ search_enabled: true
8
+ last_updated:
9
+ index_field_type: uint
10
+ search_enabled: false
11
+ bee_larvae_type:
12
+ index_field_type: literal
13
+ HoneyBadger:
14
+ name:
15
+ index_field_type: text
@@ -0,0 +1,14 @@
1
+ test:
2
+ domain_prefix: test
3
+ access_key: my_great_key
4
+ secrite_access_key: not_a_big_secret
5
+ access_permissions:
6
+ - ip_address: 192.168.66.23/32
7
+ - ip_address: 23.44.23.25/32
8
+ development:
9
+ domain_prefix: test
10
+ access_key: my_great_key
11
+ secrite_access_key: not_a_big_secret
12
+ access_permissions:
13
+ - ip_address: 192.168.66.23/32
14
+ - ip_address: 23.44.23.25/32
@@ -0,0 +1,5 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/test.sqlite3
4
+ pool: 5
5
+
@@ -0,0 +1,123 @@
1
+ require 'lib/active_asari_spec_data'
2
+ include ActiveAsariSpecData
3
+ require 'spec_helper'
4
+
5
+ describe 'active_record' do
6
+
7
+ context 'asari index' do
8
+ it 'should call asari index with the correct parameters' do
9
+ TestModel.should_receive(:asari_index).with('test-test-model-7yopqryvjnumbe547ha7xhmjwi', [:name, :amount, :last_updated, :bee_larvae_type, :active_asari_id])
10
+ ENV['RAILS_ENV'] = 'development'
11
+ ENV['RACK_ENV'] = 'development'
12
+ ActiveAsari.should_receive(:asari_domain_name).and_return 'test-test-model-7yopqryvjnumbe547ha7xhmjwi'
13
+ TestModel.send(:active_asari_index, 'TestModel')
14
+ end
15
+ end
16
+
17
+ context 'models' do
18
+ before :each do
19
+ asari_instance = double 'asari instance'
20
+ asari_instance.should_receive(:update_item).with(1, {:name => 'test', :amount => 2, :last_updated => nil, :bee_larvae_type => nil})
21
+ asari_instance.should_receive(:add_item).with(1, {:name => 'test', :amount => 2, :last_updated => '', :bee_larvae_type => ''})
22
+ TestModel.class_variable_set(:@@asari_when, nil)
23
+ TestModel.class_variable_set(:@@asari_fields, [:name, :amount, :last_updated, :bee_larvae_type])
24
+ TestModel.class_variable_set(:@@asari_instance, asari_instance)
25
+ CreateTestModel.up
26
+ @model = TestModel.create :name => 'test', :amount => 2
27
+ @model.save
28
+ end
29
+
30
+ it 'should add new records to cloud search and alias object id and active_asari_id' do
31
+ @model.id.should eq @model.active_asari_id
32
+ end
33
+
34
+ after :each do
35
+ CreateTestModel.down
36
+ end
37
+ end
38
+
39
+ context 'asari_domain_name' do
40
+ it 'should get the correct domain name and only call Amazon once' do
41
+ aws_client = double 'AWS Client'
42
+ aws_client.should_receive(:describe_domains).once.and_return DESCRIBE_DOMAINS_RESPONSE
43
+ ActiveAsari.should_receive(:aws_client).once.and_return aws_client
44
+ ActiveAsari.asari_domain_name('lance-event').should eq 'test-lance-event-7yopqryvjnumbe547ha7xhmjwi'
45
+ ActiveAsari.asari_domain_name('lance-event').should eq 'test-lance-event-7yopqryvjnumbe547ha7xhmjwi'
46
+ end
47
+ end
48
+
49
+ context 'active_asari_raw_search' do
50
+ let(:asari) {double 'Asari'}
51
+
52
+ before :each do
53
+ ActiveAsari.should_receive(:asari_domain_name).with('TestModel').and_return('test-model-666')
54
+ end
55
+
56
+ it 'should search for all available fields for a item' do
57
+ asari.should_receive(:search).with('foo', :return_fields => [:name, :amount, :last_updated, :bee_larvae_type, :active_asari_id]).and_return(
58
+ {'33' => {'name' => ['beavis'], 'amount' => ['22'], 'last_updated' => ['4543457887875']}})
59
+ Asari.should_receive(:new).with('test-model-666').and_return asari
60
+ ActiveAsari.active_asari_raw_search 'TestModel', 'foo'
61
+ end
62
+
63
+ it 'should search for all available fields for a item with a binary search' do
64
+ asari.should_receive(:search).with('foo', :return_fields => [:name, :amount, :last_updated, :bee_larvae_type, :active_asari_id], :query_type => :boolean).and_return(
65
+ {'33' => {'name' => ['beavis'], 'amount' => ['22'], 'last_updated' => ['4543457887875']}})
66
+ Asari.should_receive(:new).with('test-model-666').and_return asari
67
+ ActiveAsari.active_asari_raw_search 'TestModel', 'foo', :query_type => :boolean
68
+ end
69
+ end
70
+
71
+ context 'active_asari_search' do
72
+ let(:raw_result) {{'33' => {'name' => ['beavis'], 'amount' => ['22'], 'last_updated' => ['4543457887875']}}}
73
+ it 'should call out to do a raw search and then objectify the results' do
74
+ ActiveAsari.should_receive(:active_asari_raw_search).with('TestModel', 'foo', {}).and_return(raw_result)
75
+ ActiveAsari.should_receive(:objectify_results).with(raw_result).and_return({'33' => 'stuff'})
76
+ ActiveAsari.active_asari_search 'TestModel', 'foo'
77
+ end
78
+
79
+ it 'should pass on the parameters for a boolean search' do
80
+ ActiveAsari.should_receive(:active_asari_raw_search).with('TestModel', 'foo', :query_type => :boolean).and_return(raw_result)
81
+ ActiveAsari.should_receive(:objectify_results).with(raw_result).and_return({'33' => 'stuff'})
82
+ ActiveAsari.active_asari_search 'TestModel', 'foo', :query_type => :boolean
83
+ end
84
+ end
85
+
86
+ context 'objectify results' do
87
+ let(:hash_results) {{'33' => {'name' => ['beavis'], 'amount' => ['22', '33'], 'last_updated' => ['4543457887875']},
88
+ '34' => {'name' => ['butthead'], 'amount' => ['666'], 'last_updated' => ['454333457887875']}}}
89
+ let(:objectified_results) {ActiveAsari.objectify_results(hash_results)}
90
+
91
+ it 'should create an object with a reference to the raw results' do
92
+ objectified_results['33'].raw_result.should eq hash_results['33']
93
+ objectified_results['34'].raw_result.should eq hash_results['34']
94
+ end
95
+
96
+ context 'method_missing' do
97
+ it 'should create methods on the fly for hash items in an object' do
98
+ objectified_results['33'].name.should eq 'beavis'
99
+ objectified_results['34'].name.should eq 'butthead'
100
+ end
101
+
102
+ it 'should return a array of items is an array accessor is used' do
103
+ objectified_results['33'].amount_array.should eq ['22', '33']
104
+ objectified_results['34'].amount_array.should eq ['666']
105
+ end
106
+
107
+ it 'should raise an error if a invalid parameter is specified' do
108
+ expect {objectified_results['33'].foo}.to raise_error(NoMethodError)
109
+ end
110
+ end
111
+
112
+ context 'respond_to?' do
113
+ it 'should return true if the method exists' do
114
+ objectified_results['33'].respond_to?(:amount_array).should eq true
115
+ objectified_results['33'].respond_to?(:name).should eq true
116
+ end
117
+
118
+ it 'should return true if the method exists' do
119
+ objectified_results['33'].respond_to?(:cornholio).should eq false
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveAsariSpecData
2
+ DESCRIBE_DOMAINS_TEST_MODEL_RESPONSE = {:domain_status_list=>[{:search_partition_count=>1, :search_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:search/test-model", :endpoint=>"search-test-model-7yopqryvjnumbe547ha7xhmjwi.us-east-1.cloudsearch.amazonaws.com"}, :num_searchable_docs=>0, :search_instance_type=>"search.m1.small", :created=>true, :domain_id=>"658167492042/test-model", :processing=>false, :search_instance_count=>1, :domain_name=>"test-model", :requires_index_documents=>false, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:doc/test-model", :endpoint=>"doc-test-model-7yopqryvjnumbe547ha7xhmjwi.us-east-1.cloudsearch.amazonaws.com"}}, {:search_partition_count=>1, :search_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:search/lance", :endpoint=>"search-lance-lagja54pf5qhpzayza4awcw7wu.us-east-1.cloudsearch.amazonaws.com"}, :num_searchable_docs=>28005, :search_instance_type=>"search.m1.small", :created=>true, :domain_id=>"658167492042/lance", :processing=>false, :search_instance_count=>1, :domain_name=>"lance", :requires_index_documents=>true, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:doc/lance", :endpoint=>"doc-lance-lagja54pf5qhpzayza4awcw7wu.us-east-1.cloudsearch.amazonaws.com"}}, {:search_partition_count=>1, :search_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:search/beavis", :endpoint=>"search-beavis-bg3kl446fx2bm5s4in2rltsoui.us-east-1.cloudsearch.amazonaws.com"}, :num_searchable_docs=>0, :search_instance_type=>"search.m1.small", :created=>true, :domain_id=>"658167492042/beavis", :processing=>false, :search_instance_count=>1, :domain_name=>"beavis", :requires_index_documents=>false, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:doc/beavis", :endpoint=>"doc-beavis-bg3kl446fx2bm5s4in2rltsoui.us-east-1.cloudsearch.amazonaws.com"}}], :response_metadata=>{:request_id=>"2b782398-fb82-11e2-a681-9b8b1f7eca45"}}
3
+
4
+ DESCRIBE_DOMAINS_RESPONSE = {:domain_status_list=>[{:search_partition_count=>1, :search_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:search/test-lance-event", :endpoint=>"search-test-lance-event-7yopqryvjnumbe547ha7xhmjwi.us-east-1.cloudsearch.amazonaws.com"}, :num_searchable_docs=>0, :search_instance_type=>"search.m1.small", :created=>true, :domain_id=>"658167492042/test-lance-event", :processing=>false, :search_instance_count=>1, :domain_name=>"test-lance-event", :requires_index_documents=>false, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:doc/test-lance-event", :endpoint=>"doc-test-lance-event-7yopqryvjnumbe547ha7xhmjwi.us-east-1.cloudsearch.amazonaws.com"}}, {:search_partition_count=>1, :search_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:search/lance", :endpoint=>"search-test-lance-lagja54pf5qhpzayza4awcw7wu.us-east-1.cloudsearch.amazonaws.com"}, :num_searchable_docs=>28005, :search_instance_type=>"search.m1.small", :created=>true, :domain_id=>"658167492042/test-lance", :processing=>false, :search_instance_count=>1, :domain_name=>"test-lance", :requires_index_documents=>true, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:doc/lance", :endpoint=>"doc-test-lance-lagja54pf5qhpzayza4awcw7wu.us-east-1.cloudsearch.amazonaws.com"}}, {:search_partition_count=>1, :search_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:search/beavis", :endpoint=>"search-beavis-bg3kl446fx2bm5s4in2rltsoui.us-east-1.cloudsearch.amazonaws.com"}, :num_searchable_docs=>0, :search_instance_type=>"search.m1.small", :created=>true, :domain_id=>"658167492042/beavis", :processing=>false, :search_instance_count=>1, :domain_name=>"test-beavis", :requires_index_documents=>false, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:658167492042:doc/beavis", :endpoint=>"doc-beavis-bg3kl446fx2bm5s4in2rltsoui.us-east-1.cloudsearch.amazonaws.com"}}], :response_metadata=>{:request_id=>"2b782398-fb82-11e2-a681-9b8b1f7eca45"}}
5
+
6
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+ require 'lib/migrations_spec_data'
3
+ include MigrationsSpecData
4
+
5
+ describe 'migrations' do
6
+
7
+ let(:migrations) {ActiveAsari::Migrations.new}
8
+
9
+ before :each do
10
+ end
11
+
12
+ context 'index_field' do
13
+ context 'literal fields' do
14
+ before :each do
15
+ expected_index_field_request = {:domain_name => 'test-beavis-butthead', :index_field =>
16
+ {:index_field_name => 'band_name', :index_field_type => 'literal', :literal_options =>
17
+ {:search_enabled => false, :result_enabled => true}}}
18
+ migrations.connection.should_receive(:define_index_field).with(expected_index_field_request).and_return CREATE_LITERAL_INDEX_RESPONSE
19
+ end
20
+
21
+ it 'should add a index to the domain' do
22
+ migrations.create_index_field('BeavisButthead', 'band_name' => { 'index_field_type' => 'literal', 'search_enabled' => false})
23
+ end
24
+
25
+ it 'should default search enabled to false if it is not specified' do
26
+ migrations.create_index_field('BeavisButthead', 'band_name' => { 'index_field_type' => 'literal'})
27
+ end
28
+
29
+ it 'should default search enabled to false if it is not specified as a blank string' do
30
+ migrations.create_index_field('BeavisButthead', 'band_name' => { 'index_field_type' => 'literal', 'search_enabled' => ''})
31
+ end
32
+
33
+ end
34
+
35
+ it 'should set search enabled to true if it is a string that evaluates to true' do
36
+ expected_index_field_request = {:domain_name => 'test-beavis-butthead', :index_field =>
37
+ {:index_field_name => 'band_name', :index_field_type => 'literal', :literal_options =>
38
+ {:search_enabled => true, :result_enabled => true}}}
39
+ migrations.connection.should_receive(:define_index_field).with(expected_index_field_request).and_return CREATE_LITERAL_INDEX_RESPONSE
40
+ migrations.create_index_field('BeavisButthead', 'band_name' => { 'index_field_type' => 'literal', 'search_enabled' => true})
41
+ end
42
+
43
+ it 'should add a text index to the domain' do
44
+ expected_index_field_request = {:domain_name => 'test-beavis', :index_field =>
45
+ {:index_field_name => 'tv_location', :index_field_type => 'text', :text_options =>
46
+ {:result_enabled => true}}}
47
+ migrations.connection.should_receive(:define_index_field).with(expected_index_field_request).and_return CREATE_TEXT_INDEX_RESPONSE
48
+ migrations.create_index_field('beavis', 'tv_location' => { 'index_field_type' => 'text'})
49
+ end
50
+
51
+ it 'should add a uint index to the domain' do
52
+ expected_index_field_request = {:domain_name => 'test-beavis', :index_field =>
53
+ {:index_field_name => 'num_tvs', :index_field_type => 'uint'}}
54
+ migrations.connection.should_receive(:define_index_field).with(expected_index_field_request).and_return CREATE_UINT_INDEX_RESPONSE
55
+ migrations.create_index_field('beavis', 'num_tvs' => { 'index_field_type' => 'uint'})
56
+ end
57
+ end
58
+
59
+ context 'update_service_access_policies' do
60
+ it 'should allow all access for ip addresses specified in the configuration file' do
61
+ access_policies = "{\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\",\"Condition\":{\"IpAddress\":{\"aws:SourceIp\":[\"192.168.66.23/32\"]}}},{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\",\"Condition\":{\"IpAddress\":{\"aws:SourceIp\":[\"23.44.23.25/32\"]}}}]}"
62
+ migrations.connection.should_receive(:update_service_access_policies).with(:domain_name => 'beavis',
63
+ :access_policies => access_policies)
64
+ ENV['RAILS_ENV'] = nil
65
+ ENV['RACK_ENV'] = 'test'
66
+ migrations.update_service_access_policies 'beavis'
67
+ end
68
+ end
69
+
70
+ context 'domain' do
71
+
72
+ it 'should create a domain if one doesnt exist' do
73
+ migrations.connection.should_receive(:create_domain).with({:domain_name => 'test-beavis-butthead'}).and_return CREATE_DOMAIN_RESPONSE
74
+ migrations.create_domain 'BeavisButthead'
75
+ end
76
+
77
+ it 'should create indexes for all items in the domain and create the domain' do
78
+ migrations.should_receive(:create_domain).once.with 'TestModel'
79
+ migrations.should_receive(:create_index_field).once.with('TestModel',
80
+ 'name' => { 'index_field_type' => 'text',
81
+ 'search_enabled' => true})
82
+ migrations.should_receive(:create_index_field).once.with('TestModel',
83
+ 'amount' => { 'index_field_type' => 'uint',
84
+ 'search_enabled' => true})
85
+ migrations.should_receive(:create_index_field).once.with('TestModel',
86
+ 'last_updated' => { 'index_field_type' => 'uint',
87
+ 'search_enabled' => false})
88
+ migrations.should_receive(:create_index_field).once.with('TestModel',
89
+ 'bee_larvae_type' => { 'index_field_type' => 'literal'})
90
+ migrations.should_receive(:create_index_field).once.with('TestModel',
91
+ 'active_asari_id' => { 'index_field_type' => 'uint'})
92
+ ActiveAsari.should_receive(:amazon_safe_domain_name).twice.with('TestModel').and_return 'test-model-666'
93
+ migrations.should_receive(:update_service_access_policies).once.with('test-model-666')
94
+ migrations.connection.should_receive(:index_documents).with(:domain_name => 'test-model-666')
95
+ migrations.migrate_domain 'TestModel'
96
+ end
97
+ end
98
+
99
+ context 'migrate_all' do
100
+ it 'should migrate all of the domains' do
101
+ migrations.should_receive(:migrate_domain).once.with 'TestModel'
102
+ migrations.should_receive(:migrate_domain).once.with 'HoneyBadger'
103
+ migrations.migrate_all
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,9 @@
1
+ module MigrationsSpecData
2
+
3
+ CREATE_DOMAIN_RESPONSE = {:domain_status=>{:search_partition_count=>0, :search_service=>{:arn=>"arn:aws:cs:us-east-1:888167492042:search/beavis"}, :num_searchable_docs=>0, :created=>true, :domain_id=>"888167492042/beavis", :processing=>false, :search_instance_count=>0, :domain_name=>"beavis", :requires_index_documents=>false, :deleted=>false, :doc_service=>{:arn=>"arn:aws:cs:us-east-1:888167492042:doc/beavis"}}, :response_metadata=>{:request_id=>"88e3adcb-f999-11e2-ba8b-ab9a7c0903a8"}}
4
+ CREATE_LITERAL_INDEX_RESPONSE = {:index_field=>{:status=>{:creation_date=>'2013-07-30 20:47:55 UTC', :pending_deletion=>"false", :update_version=>20, :state=>"RequiresIndexDocuments", :update_date=>'2013-07-30 20:47:55 UTC'}, :options=>{:source_attributes=>[], :literal_options=>{:search_enabled=>false}, :index_field_type=>"literal", :index_field_name=>"test"}}, :response_metadata=>{:request_id=>"8885505e-f959-11e2-b89b-2d5c6f978750"}}
5
+ CREATE_TEXT_INDEX_RESPONSE = {:index_field=>{:status=>{:creation_date=>'2013-07-30 20:47:55 UTC', :pending_deletion=>"false", :update_version=>20, :state=>"RequiresIndexDocuments", :update_date=>'2013-07-30 20:47:55 UTC'}, :options=>{:source_attributes=>[], :text_options=>{:result_enabled=>true}, :index_field_type=>"text", :index_field_name=>"test"}}, :response_metadata=>{:request_id=>"8885505e-f959-11e2-b89b-2d5c6f978750"}}
6
+ CREATE_UINT_INDEX_RESPONSE = {:index_field=>{:status=>{:creation_date=>'2013-07-30 20:47:55 UTC', :pending_deletion=>"false", :update_version=>20, :state=>"RequiresIndexDocuments", :update_date=>'2013-07-30 20:47:55 UTC'}, :options=>{:source_attributes=>[], :index_field_type=>"uint", :index_field_name=>"num_tvs"}}, :response_metadata=>{:request_id=>"8885505e-f959-11e2-b89b-2d5c6f978750"}}
7
+ end
8
+
9
+
@@ -0,0 +1,14 @@
1
+ class CreateTestModel < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :test_models do |t|
4
+ t.string :name
5
+ t.integer :amount
6
+ t.datetime :last_updated
7
+ t.string :bee_larvae_type
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :test_models
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+
2
+ class AWS::CloudSearch::Client
3
+ def initialize
4
+ end
5
+
6
+ def describe_domains
7
+ ActiveAsariSpecData::DESCRIBE_DOMAINS_TEST_MODEL_RESPONSE
8
+ end
9
+ end
10
+
11
+ class TestModel < ActiveRecord::Base
12
+ include Asari::ActiveRecord
13
+ include ActiveAsari::ActiveRecord
14
+ active_asari_index 'TestModel'
15
+ end
@@ -0,0 +1,22 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ require 'active_record'
5
+ require 'rspec'
6
+ require 'factory_girl'
7
+ require 'rspec/autorun'
8
+ db_config = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + '/database.yml')
9
+ ActiveRecord::Base.establish_connection db_config['test']
10
+
11
+ ActiveRecord::Base.connection
12
+
13
+ ACTIVE_ASARI_SEARCH_DOMAIN = 'my_great_domain'
14
+ Dir[File.dirname(__FILE__) + "/../lib/active_asari/*.rb"].each {|file| require file }
15
+ require 'active_asari'
16
+ ACTIVE_ASARI_CONFIG, ACTIVE_ASARI_ENV = ActiveAsari.configure(File.dirname(__FILE__))
17
+ require 'active_asari/active_record'
18
+ require 'asari'
19
+ require 'aws'
20
+ require 'model/test_model'
21
+ require 'model/create_test_model'
22
+
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active-asari
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Lance Gleason
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: asari
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aws-sdk
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: ActiveAsari is a Ruby interface for AWS CloudSearch. It uses Asari as
63
+ a foundation and make it work more like Active Record.
64
+ email:
65
+ - lgleason@polyglotprogramminginc.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - LICENSE
74
+ - README.md
75
+ - Rakefile
76
+ - active-asari.gemspec
77
+ - circle.yml
78
+ - db/test.sqlite3
79
+ - lib/active_asari.rb
80
+ - lib/active_asari/active_record.rb
81
+ - lib/active_asari/common.rb
82
+ - lib/active_asari/migrations.rb
83
+ - lib/active_asari/result_object.rb
84
+ - spec/active_asari_config.yml
85
+ - spec/active_asari_env.yml
86
+ - spec/database.yml
87
+ - spec/lib/active_asari_spec.rb
88
+ - spec/lib/active_asari_spec_data.rb
89
+ - spec/lib/migrations_spec.rb
90
+ - spec/lib/migrations_spec_data.rb
91
+ - spec/model/create_test_model.rb
92
+ - spec/model/test_model.rb
93
+ - spec/spec_helper.rb
94
+ homepage: http://github.com/playon/active-asari
95
+ licenses: []
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project: asari
114
+ rubygems_version: 1.8.25
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: ActiveAsari is a Ruby interface for AWS CloudSearch.
118
+ test_files:
119
+ - spec/active_asari_config.yml
120
+ - spec/active_asari_env.yml
121
+ - spec/database.yml
122
+ - spec/lib/active_asari_spec.rb
123
+ - spec/lib/active_asari_spec_data.rb
124
+ - spec/lib/migrations_spec.rb
125
+ - spec/lib/migrations_spec_data.rb
126
+ - spec/model/create_test_model.rb
127
+ - spec/model/test_model.rb
128
+ - spec/spec_helper.rb