├── .rspec ├── lib └── algoliasearch │ ├── version.rb │ ├── algolia_job.rb │ ├── railtie.rb │ ├── tasks │ └── algoliasearch.rake │ ├── pagination │ ├── will_paginate.rb │ ├── pagy.rb │ └── kaminari.rb │ ├── pagination.rb │ ├── configuration.rb │ └── utilities.rb ├── .document ├── Dockerfile ├── MAINTAINERS.md ├── .dockerignore ├── .gitignore ├── Rakefile ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── issue.yml ├── spec ├── utilities_spec.rb └── spec_helper.rb ├── LICENSE ├── Gemfile ├── DOCKER_README.MD ├── .circleci └── config.yml ├── UPGRADING_TO_V3.MD ├── algoliasearch-rails.gemspec ├── CONTRIBUTING.md ├── vendor └── assets │ └── javascripts │ └── algolia │ ├── bloodhound.min.js │ ├── typeahead.jquery.min.js │ ├── algoliasearch.min.js │ ├── v2 │ ├── algoliasearch.min.js │ └── algoliasearch.jquery.min.js │ ├── algoliasearch.jquery.min.js │ └── algoliasearch.angular.min.js └── CHANGELOG.MD /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /lib/algoliasearch/version.rb: -------------------------------------------------------------------------------- 1 | module AlgoliaSearch 2 | VERSION = '3.0.2' 3 | end 4 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.6.3 2 | 3 | RUN gem install bundler 4 | 5 | WORKDIR /app 6 | COPY . /app/ 7 | RUN bundle install 8 | -------------------------------------------------------------------------------- /lib/algoliasearch/algolia_job.rb: -------------------------------------------------------------------------------- 1 | module AlgoliaSearch 2 | class AlgoliaJob < ::ActiveJob::Base 3 | queue_as { AlgoliaSearch.configuration[:queue_name] } 4 | 5 | def perform(record, method) 6 | record.send(method) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/algoliasearch/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'rails' 2 | 3 | module AlgoliaSearch 4 | class Railtie < Rails::Railtie 5 | rake_tasks do 6 | load "algoliasearch/tasks/algoliasearch.rake" 7 | end 8 | end 9 | class Engine < Rails::Engine 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## `algolia/algoliasearch-rails` maintainers 2 | 3 | | Name | Email | 4 | |-----------------|-----------------------------| 5 | | Julien Bourdeau | julien.bourdeau@algolia.com | 6 | | Matthieu Dumont | matthieu.dumont@algolia.com | 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | .rvmrc 21 | 22 | ## PROJECT::SPECIFIC 23 | data.sqlite3 24 | sequel_data.sqlite3 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | .rvmrc 21 | Gemfile.lock 22 | debug.log 23 | .ruby-version 24 | 25 | ## PROJECT::SPECIFIC 26 | data.sqlite3 27 | sequel_data.sqlite3 28 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | require 'rdoc/task' 5 | Rake::RDocTask.new do |rdoc| 6 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 7 | 8 | rdoc.rdoc_dir = 'rdoc' 9 | rdoc.title = "AlgoliaSearch Rails #{version}" 10 | rdoc.rdoc_files.include('README*') 11 | rdoc.rdoc_files.include('lib/**/*.rb') 12 | end 13 | 14 | require "rspec/core/rake_task" 15 | RSpec::Core::RakeTask.new(:spec) 16 | 17 | task :default => :spec 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | - Rails version: 15 | - Algolia Rails integration version: 16 | - Algolia Client Version: #.#.# 17 | - Language Version: 18 | 19 | ### Description 20 | 21 | 22 | ### Steps To Reproduce 23 | -------------------------------------------------------------------------------- /lib/algoliasearch/tasks/algoliasearch.rake: -------------------------------------------------------------------------------- 1 | namespace :algoliasearch do 2 | 3 | desc "Reindex all models" 4 | task :reindex => :environment do 5 | AlgoliaSearch::Utilities.reindex_all_models 6 | end 7 | 8 | desc "Set settings to all indexes" 9 | task :set_all_settings => :environment do 10 | AlgoliaSearch::Utilities.set_settings_all_models 11 | end 12 | 13 | desc "Clear all indexes" 14 | task :clear_indexes => :environment do 15 | puts "clearing all indexes" 16 | AlgoliaSearch::Utilities.clear_all_indexes 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/algoliasearch/pagination/will_paginate.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'will_paginate/collection' 3 | rescue LoadError 4 | raise(AlgoliaSearch::BadConfiguration, "AlgoliaSearch: Please add 'will_paginate' to your Gemfile to use will_paginate pagination backend") 5 | end 6 | 7 | module AlgoliaSearch 8 | module Pagination 9 | class WillPaginate 10 | def self.create(results, total_hits, options = {}) 11 | ::WillPaginate::Collection.create(options[:page], options[:per_page], total_hits) { |pager| pager.replace results } 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | | Q | A 2 | | ----------------- | ---------- 3 | | Bug fix? | yes/no 4 | | New feature? | yes/no 5 | | BC breaks? | no 6 | | Related Issue | Fix #... 7 | | Need Doc update | yes/no 8 | 9 | 10 | ## Describe your change 11 | 12 | 16 | 17 | ## What problem is this fixing? 18 | 19 | 23 | -------------------------------------------------------------------------------- /lib/algoliasearch/pagination/pagy.rb: -------------------------------------------------------------------------------- 1 | unless defined? Pagy 2 | raise(AlgoliaSearch::BadConfiguration, "AlgoliaSearch: Please add 'pagy' to your Gemfile to use Pagy pagination backend") 3 | end 4 | 5 | module AlgoliaSearch 6 | module Pagination 7 | class Pagy 8 | 9 | def self.create(results, total_hits, options = {}) 10 | vars = { 11 | count: total_hits, 12 | page: options[:page], 13 | items: options[:per_page] 14 | } 15 | 16 | pagy_version = Gem::Version.new(::Pagy::VERSION) 17 | pagy = if pagy_version >= Gem::Version.new('9.0') 18 | ::Pagy.new(**vars) 19 | else 20 | ::Pagy.new(vars) 21 | end 22 | 23 | [pagy, results] 24 | end 25 | end 26 | 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /lib/algoliasearch/pagination.rb: -------------------------------------------------------------------------------- 1 | module AlgoliaSearch 2 | module Pagination 3 | 4 | autoload :WillPaginate, 'algoliasearch/pagination/will_paginate' 5 | autoload :Kaminari, 'algoliasearch/pagination/kaminari' 6 | autoload :Pagy, 'algoliasearch/pagination/pagy' 7 | 8 | def self.create(results, total_hits, options = {}) 9 | return results if AlgoliaSearch.configuration[:pagination_backend].nil? 10 | begin 11 | backend = AlgoliaSearch.configuration[:pagination_backend].to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } # classify pagination backend name 12 | page = Object.const_get(:AlgoliaSearch).const_get(:Pagination).const_get(backend).create(results, total_hits, options) 13 | page 14 | rescue NameError 15 | raise(BadConfiguration, "Unknown pagination backend") 16 | end 17 | end 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/utilities_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper')) 2 | 3 | AlgoliaSearch.configuration = { :application_id => ENV['ALGOLIA_APPLICATION_ID'], :api_key => ENV['ALGOLIA_API_KEY'] } 4 | 5 | describe AlgoliaSearch::Utilities do 6 | 7 | before(:each) do 8 | @included_in = AlgoliaSearch.instance_variable_get :@included_in 9 | AlgoliaSearch.instance_variable_set :@included_in, [] 10 | 11 | class Dummy 12 | include AlgoliaSearch 13 | 14 | def self.model_name 15 | "Dummy" 16 | end 17 | 18 | algoliasearch 19 | end 20 | end 21 | 22 | after(:each) do 23 | AlgoliaSearch.instance_variable_set :@included_in, @included_in 24 | end 25 | 26 | it "should get the models where AlgoliaSearch module was included" do 27 | (AlgoliaSearch::Utilities.get_model_classes - [Dummy]).should == [] 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-Present Algolia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem 'json', '>= 1.5.1' 4 | gem 'algolia', '>= 3.12.0' 5 | 6 | if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx' 7 | gem 'rubysl', '~> 2.0', :platform => :rbx 8 | end 9 | 10 | group :test do 11 | rails_version = ENV["RAILS_VERSION"] || '6.1' 12 | gem 'rails', "~> #{rails_version}" 13 | gem 'active_model_serializers' 14 | if Gem::Version.new(rails_version) >= Gem::Version.new('6.0') 15 | gem 'sqlite3', '~> 1.4.0', :platform => [:rbx, :ruby] 16 | else 17 | gem 'sqlite3', '< 1.4.0', :platform => [:rbx, :ruby] 18 | end 19 | gem 'rspec', '~> 3.0' 20 | gem 'jdbc-sqlite3', :platform => :jruby 21 | gem 'activerecord-jdbc-adapter', :platform => :jruby 22 | gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby 23 | gem 'redgreen' 24 | 25 | sequel_version = ENV['SEQUEL_VERSION'] ? "~> #{ENV['SEQUEL_VERSION']}" : '>= 4.0' 26 | gem 'sequel', sequel_version 27 | end 28 | 29 | group :development do 30 | gem 'rake', '>= 10.1.0' 31 | gem 'rdoc' 32 | end 33 | 34 | group :test, :development do 35 | gem 'will_paginate', '>= 2.3.15' 36 | gem 'kaminari', '< 1' 37 | gem 'pagy' 38 | end 39 | 40 | -------------------------------------------------------------------------------- /lib/algoliasearch/configuration.rb: -------------------------------------------------------------------------------- 1 | module AlgoliaSearch 2 | module Configuration 3 | def initialize 4 | @client = nil 5 | end 6 | 7 | def configuration 8 | @@configuration || raise(NotConfigured, "Please configure AlgoliaSearch. Set AlgoliaSearch.configuration = {application_id: 'YOUR_APPLICATION_ID', api_key: 'YOUR_API_KEY'}") 9 | end 10 | 11 | def configuration=(configuration) 12 | @@configuration = default_configuration 13 | .merge(configuration) 14 | end 15 | 16 | def client 17 | if @client.nil? 18 | setup_client 19 | end 20 | 21 | @client 22 | end 23 | 24 | def setup_client 25 | @client = Algolia::SearchClient.create( 26 | @@configuration[:application_id], 27 | @@configuration[:api_key], 28 | { 29 | user_agent_segments: [ 30 | "Algolia for Rails (#{AlgoliaSearch::VERSION})", 31 | "Rails (#{defined?(::Rails::VERSION::STRING) ? ::Rails::VERSION::STRING : 'unknown'})", 32 | @@configuration[:append_to_user_agent] 33 | ].compact 34 | }) 35 | end 36 | 37 | def default_configuration 38 | { 39 | queue_name: 'algoliasearch' 40 | } 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/algoliasearch/utilities.rb: -------------------------------------------------------------------------------- 1 | module AlgoliaSearch 2 | module Utilities 3 | class << self 4 | def get_model_classes 5 | if Rails.application && defined?(Rails.autoloaders) && Rails.autoloaders.zeitwerk_enabled? 6 | Zeitwerk::Loader.eager_load_all 7 | elsif Rails.application 8 | Rails.application.eager_load! 9 | end 10 | AlgoliaSearch.instance_variable_get :@included_in 11 | end 12 | 13 | def clear_all_indexes 14 | get_model_classes.each do |klass| 15 | klass.clear_index! 16 | end 17 | end 18 | 19 | def reindex_all_models 20 | klasses = get_model_classes 21 | 22 | puts '' 23 | puts "Reindexing #{klasses.count} models: #{klasses.to_sentence}." 24 | puts '' 25 | 26 | klasses.each do |klass| 27 | puts klass 28 | puts "Reindexing #{klass.count} records..." 29 | klass.algolia_reindex 30 | end 31 | end 32 | 33 | def set_settings_all_models 34 | klasses = get_model_classes 35 | 36 | puts '' 37 | puts "Pushing settings for #{klasses.count} models: #{klasses.to_sentence}." 38 | puts '' 39 | 40 | klasses.each do |klass| 41 | puts "Pushing #{klass} settings..." 42 | klass.algolia_set_settings 43 | end 44 | end 45 | end 46 | end 47 | end 48 | 49 | -------------------------------------------------------------------------------- /lib/algoliasearch/pagination/kaminari.rb: -------------------------------------------------------------------------------- 1 | unless defined? Kaminari 2 | raise(AlgoliaSearch::BadConfiguration, "AlgoliaSearch: Please add 'kaminari' to your Gemfile to use kaminari pagination backend") 3 | end 4 | 5 | require "kaminari/models/array_extension" 6 | 7 | module AlgoliaSearch 8 | module Pagination 9 | class Kaminari < ::Kaminari::PaginatableArray 10 | 11 | def initialize(array, options) 12 | super(array, **options) 13 | end 14 | 15 | def limit(num) 16 | # noop 17 | self 18 | end 19 | 20 | def offset(num) 21 | # noop 22 | self 23 | end 24 | 25 | class << self 26 | def create(results, total_hits, options = {}) 27 | offset = ((options[:page] - 1) * options[:per_page]) 28 | array = new results, :offset => offset, :limit => options[:per_page], :total_count => total_hits 29 | if array.empty? and !results.empty? 30 | # since Kaminari 0.16.0, you need to pad the results with nil values so it matches the offset param 31 | # otherwise you'll get an empty array: https://github.com/amatsuda/kaminari/commit/29fdcfa8865f2021f710adaedb41b7a7b081e34d 32 | results = ([nil] * offset) + results 33 | array = new results, :offset => offset, :limit => options[:per_page], :total_count => total_hits 34 | end 35 | array 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | require 'timeout' 4 | 5 | Bundler.setup :test 6 | 7 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 8 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 9 | 10 | require 'algoliasearch-rails' 11 | require 'rspec' 12 | require 'rails/all' 13 | 14 | raise "missing ALGOLIA_APPLICATION_ID or ALGOLIA_API_KEY environment variables" if ENV['ALGOLIA_APPLICATION_ID'].nil? || ENV['ALGOLIA_API_KEY'].nil? 15 | 16 | Thread.current[:algolia_hosts] = nil 17 | 18 | GlobalID.app = 'algoiasearch-rails' 19 | 20 | RSpec.configure do |c| 21 | c.mock_with :rspec 22 | c.filter_run :focus => true 23 | c.run_all_when_everything_filtered = true 24 | c.formatter = 'documentation' 25 | 26 | c.around(:each) do |example| 27 | Timeout::timeout(120) { 28 | example.run 29 | } 30 | end 31 | 32 | # Remove all indexes setup in this run in local or CI 33 | c.after(:suite) do 34 | safe_index_list.each do |i| 35 | begin 36 | res = AlgoliaSearch.client.delete_index(i.name) 37 | AlgoliaSearch.client.wait_for_task(i.name, res.task_id) 38 | rescue 39 | # fail gracefully 40 | end 41 | end 42 | end 43 | end 44 | 45 | # A unique prefix for your test run in local or CI 46 | SAFE_INDEX_PREFIX = "rails_#{SecureRandom.hex(8)}".freeze 47 | 48 | # avoid concurrent access to the same index in local or CI 49 | def safe_index_name(name) 50 | "#{SAFE_INDEX_PREFIX}_#{name}" 51 | end 52 | 53 | # get a list of safe indexes in local or CI 54 | def safe_index_list 55 | list = AlgoliaSearch.client.list_indices.items 56 | list = list.select { |index| index.name.include?(SAFE_INDEX_PREFIX) } 57 | list.sort_by { |index| index.primary || "" } 58 | end 59 | -------------------------------------------------------------------------------- /.github/workflows/issue.yml: -------------------------------------------------------------------------------- 1 | name: 'Issue sync with Jira' 2 | on: 3 | issues: 4 | types: [opened] 5 | 6 | permissions: 7 | issues: write 8 | contents: read 9 | 10 | jobs: 11 | sync: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Create ticket 15 | uses: actions/github-script@v7 16 | with: 17 | script: | 18 | const action = context.payload.action; 19 | if (action !== 'opened') { 20 | return; 21 | } 22 | const title = context.payload.issue.title; 23 | const body = context.payload.issue.body; 24 | 25 | const res = await fetch('https://algolia.atlassian.net/rest/api/2/issue', { 26 | method: 'POST', 27 | headers: { 28 | 'Accept': 'application/json', 29 | 'Content-Type': 'application/json', 30 | 'Authorization': `Basic ${{ secrets.JIRA_TOKEN }}` 31 | }, 32 | body: JSON.stringify({ 33 | fields: { 34 | description: `Issue created by ${context.actor} at ${context.payload.issue.html_url} \n\n${body}`, 35 | issuetype: { 36 | id: '10001' 37 | }, 38 | parent: { 39 | key: 'API-1' 40 | }, 41 | project: { 42 | id: '11316' 43 | }, 44 | summary: `[GH-ISSUE] ${title}` 45 | }, 46 | update: {} 47 | }) 48 | }); 49 | 50 | if (!res.ok) { 51 | throw new Error(`Failed to create ticket: ${res.statusText} (${res.status}) - ${await res.text()}`); 52 | } 53 | 54 | const data = await res.json(); 55 | console.log(`Created ticket: ${data.key}`); 56 | -------------------------------------------------------------------------------- /DOCKER_README.MD: -------------------------------------------------------------------------------- 1 | In this page you will find our recommended way of installing Docker on your machine. 2 | This guide is made for OSX users. 3 | 4 | ## Install docker 5 | 6 | First install Docker using [Homebrew](https://brew.sh/) 7 | ``` 8 | $ brew install docker 9 | ``` 10 | 11 | You can then install [Docker Desktop](https://docs.docker.com/get-docker/) if you wish, or use `docker-machine`. As we prefer the second option, we will only document this one. 12 | 13 | ## Setup your docker 14 | 15 | Install `docker-machine` 16 | ``` 17 | $ brew install docker-machine 18 | ``` 19 | 20 | Then install [VirtualBox](https://www.virtualbox.org/) with [Homebrew Cask](https://github.com/Homebrew/homebrew-cask) to get a driver for your Docker machine 21 | ``` 22 | $ brew cask install virtualbox 23 | ``` 24 | 25 | You may need to enter your password and authorize the application in your `System Settings` > `Security & Privacy`. 26 | 27 | Create now a new machine, set it up as default and connect your shell to it (here we use zsh. The commands should anyway be displayed in each steps' output) 28 | 29 | ``` 30 | $ docker-machine create --driver virtualbox default 31 | $ docker-machine env default 32 | $ eval "$(docker-machine env default)" 33 | ``` 34 | 35 | Now you're all setup to use our provided Docker image! 36 | 37 | ## Build the image 38 | 39 | ```bash 40 | docker build -t algolia-rails . 41 | ``` 42 | 43 | ## Run the image 44 | 45 | You need to provide few environment variables at runtime to be able to run the [Common Test Suite](https://github.com/algolia/algoliasearch-client-specs/tree/master/common-test-suite). 46 | You can set them up directly in the command: 47 | 48 | ```bash 49 | docker run -it --rm --env ALGOLIA_APP_ID=XXXXXX [...] -v $PWD:/app -w /app algolia-rails bash 50 | ``` 51 | 52 | However, we advise you to export them in your `.bashrc` or `.zshrc`. That way, you can use [Docker's shorten syntax](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file) to set your variables. 53 | 54 | ```bash 55 | docker run -it --rm --env ALGOLIA_APPLICATION_ID \ 56 | --env ALGOLIA_API_KEY \ 57 | -v $PWD:/app -w /app algolia-rails bash 58 | ``` 59 | 60 | Once your container is running, any changes you make in your IDE are directly reflected in the container. 61 | 62 | To launch the tests, you can use one of the following commands 63 | ```shell script 64 | 65 | # run the whole test suite 66 | bundle exec rspec 67 | 68 | # run a single test 69 | bundle exec rspec ./path/to/test_spec.rb:#line_number 70 | ``` 71 | 72 | Feel free to contact us if you have any questions. 73 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | aliases: 2 | - &credentials 3 | name: Retrieve temporary Algolia credentials if needed 4 | command: | 5 | if [ "$CIRCLE_PR_REPONAME" ]; then 6 | curl -s https://algoliasearch-client-keygen.herokuapp.com | sh >> $BASH_ENV 7 | fi 8 | 9 | - &install_sqlite 10 | name: Install SQLite 11 | command: sudo apt-get update && sudo apt-get install -y sqlite3 libsqlite3-dev 12 | 13 | - &check_bundler 14 | name: Which bundler? 15 | command: bundle -v 16 | 17 | - &restore_cache 18 | name: Restore Bundler cache 19 | keys: 20 | - rails-cache-<< parameters.version >>-<< parameters.rails-version >>-<< parameters.sequel-version >>-{{ checksum "Gemfile" }} 21 | - rails-cache-<< parameters.version >>-<< parameters.rails-version >>-<< parameters.sequel-version >>- 22 | 23 | - &install_bundler 24 | name: Bundle Install 25 | command: bundle check || bundle install 26 | 27 | - &save_cache 28 | name: Save Bundler cache 29 | key: rails-cache-<< parameters.version >>-<< parameters.rails-version >>-<< parameters.sequel-version >>-{{ checksum "Gemfile" }} 30 | paths: 31 | - vendor/bundle 32 | 33 | - &run_tests 34 | name: Run unit and integration tests 35 | command: | 36 | bundle exec rake 37 | 38 | references: 39 | default_docker_ruby_executor: &default_docker_ruby_executor 40 | image: cimg/ruby:<< parameters.version >> 41 | environment: 42 | BUNDLE_JOBS: 3 43 | BUNDLE_RETRY: 3 44 | BUNDLE_PATH: vendor/bundle 45 | RAILS_VERSION: << parameters.rails-version >> 46 | SEQUEL_VERSION: << parameters.sequel-version >> 47 | 48 | version: 2.1 49 | 50 | jobs: 51 | test: 52 | description: Build, unit and integration tests 53 | parameters: 54 | version: 55 | type: string 56 | rails-version: 57 | type: string 58 | sequel-version: 59 | type: string 60 | docker: 61 | - *default_docker_ruby_executor 62 | steps: 63 | - checkout 64 | - run: *install_sqlite 65 | - run: *check_bundler 66 | - restore_cache: *restore_cache 67 | - run: *install_bundler 68 | - save_cache: *save_cache 69 | - run: *credentials 70 | - run: *run_tests 71 | 72 | workflows: 73 | version: 2 74 | ci: 75 | jobs: 76 | - test: 77 | name: "Ruby << matrix.version >> - Rails << matrix.rails-version >>" 78 | matrix: 79 | parameters: 80 | version: [ '2.5', '2.6', '2.7', '3.0', '3.1' ] 81 | rails-version: [ '6.0', '6.1' ] 82 | sequel-version: [ '5.0' ] 83 | - test: 84 | name: "Ruby << matrix.version >> - Rails << matrix.rails-version >>" 85 | matrix: 86 | parameters: 87 | version: ['3.0', '3.1'] 88 | rails-version: ['7.0'] 89 | sequel-version: ['5.0'] -------------------------------------------------------------------------------- /UPGRADING_TO_V3.MD: -------------------------------------------------------------------------------- 1 | Version 3 of the `algoliasearch-rails` gem replaces the Algolia API client version in use from version 2 to version 3. 2 | These versions of the API client differ significantly, so you likely need to make code changes when updating. 3 | We've tried keeping most of the changes internal, but there are still some breaking changes you need to be aware of when upgrading. 4 | 5 | If you encounter any breaking changes to the Rail integration that are not listed here, please open a Pull Request to add them to this list. 6 | 7 | ## Breaking changes 8 | 9 | `algolia_ensure_init` (this method is protected and shouldn't be called manually, but we list it here anyways): the method no longer returns an initialized `index` object as this is not part of the new API client. The method now returns nothing, but it still ensures the index exists and applies settings if needed. 10 | 11 | --- 12 | `Model.search`, `Model.raw_search`: response keys in the new API client are no longer strings, but are *always* symbols. For example: 13 | ```ruby 14 | # Before 15 | results = Product.raw_search('shirt') 16 | p results['hits'] 17 | 18 | # After 19 | results = Product.raw_search('shirt') 20 | p results[:hits] 21 | ``` 22 | --- 23 | `Model.search_for_facet_values`: this no longer returns an array of hashes, but an array of objects of type `Algolia::Search::FacetHits`: 24 | ```ruby 25 | # Before 26 | facets = Color.search_for_facet_values('short_name', 'bl', :query => 'black') 27 | puts facets.first['value'] 28 | 29 | # After 30 | facets = Color.search_for_facet_values('short_name', 'bl', :query => 'black') 31 | facets.first.value 32 | ``` 33 | 34 | --- 35 | `Model.index_name` takes an additional, optional parameter. You can use this if you want to get the name of one of your replica indices, which ensures the index naming takes configuration that modifies the index name into account. 36 | For example, if you have the `:per_environment` option set to true, it will automatically add the environment name in the index name. 37 | ```ruby 38 | def Product 39 | include AlgoliaSearch 40 | 41 | algoliasearch({ per_environment: true }) do 42 | add_replica 'Suits', per_environment: true do 43 | # replica settings 44 | end 45 | end 46 | 47 | end 48 | main_index_name = Product.index_name 49 | replica_index_name = Product.index_name('Suits') 50 | ``` 51 | 52 | --- 53 | `AlgoliaSearch::Configuration.client_opts`, `AlgoliaSearch::Configuration::REQUIRED_CONFIGURATION` and `AlgoliaSearch::SafeIndex` have been removed. 54 | If you need to configure the API client other than the ways that are provided now, it's recommended to set up an instance manually. 55 | 56 | --- 57 | `Model.index` and `Model.algolia_index` have been removed, as there is no notion of an `Index` object in the new version of the API clients. 58 | Instead, you can use `Model.index_name` to get the name of the index to target, and use this on an instance of the API client directly. 59 | 60 | ```ruby 61 | # Before 62 | res = Product.index.search('shoe') 63 | 64 | # After 65 | res = AlgoliaSearch.client.search_single_index(Product.index_name, { query: 'shoe' }) 66 | ``` 67 | -------------------------------------------------------------------------------- /algoliasearch-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require File.join(File.dirname(__FILE__), 'lib', 'algoliasearch', 'version') 4 | 5 | require 'date' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "algoliasearch-rails" 9 | s.version = AlgoliaSearch::VERSION 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 12 | s.authors = ["Algolia"] 13 | s.date = Date.today 14 | s.description = "AlgoliaSearch integration to your favorite ORM" 15 | s.email = "contact@algolia.com" 16 | s.extra_rdoc_files = [ 17 | "CHANGELOG.MD", 18 | "LICENSE", 19 | "README.md" 20 | ] 21 | s.files = [ 22 | ".document", 23 | ".rspec", 24 | "CHANGELOG.MD", 25 | "Gemfile", 26 | "Gemfile.lock", 27 | "LICENSE", 28 | "README.md", 29 | "Rakefile", 30 | "algoliasearch-rails.gemspec", 31 | "lib/algoliasearch-rails.rb", 32 | "lib/algoliasearch/algolia_job.rb", 33 | "lib/algoliasearch/configuration.rb", 34 | "lib/algoliasearch/pagination.rb", 35 | "lib/algoliasearch/pagination/kaminari.rb", 36 | "lib/algoliasearch/pagination/pagy.rb", 37 | "lib/algoliasearch/pagination/will_paginate.rb", 38 | "lib/algoliasearch/railtie.rb", 39 | "lib/algoliasearch/tasks/algoliasearch.rake", 40 | "lib/algoliasearch/utilities.rb", 41 | "lib/algoliasearch/version.rb", 42 | "spec/spec_helper.rb", 43 | "spec/utilities_spec.rb", 44 | "vendor/assets/javascripts/algolia/algoliasearch.angular.js", 45 | "vendor/assets/javascripts/algolia/algoliasearch.angular.min.js", 46 | "vendor/assets/javascripts/algolia/algoliasearch.jquery.js", 47 | "vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js", 48 | "vendor/assets/javascripts/algolia/algoliasearch.js", 49 | "vendor/assets/javascripts/algolia/algoliasearch.min.js", 50 | "vendor/assets/javascripts/algolia/bloodhound.js", 51 | "vendor/assets/javascripts/algolia/bloodhound.min.js", 52 | "vendor/assets/javascripts/algolia/typeahead.bundle.js", 53 | "vendor/assets/javascripts/algolia/typeahead.bundle.min.js", 54 | "vendor/assets/javascripts/algolia/typeahead.jquery.js", 55 | "vendor/assets/javascripts/algolia/typeahead.jquery.min.js", 56 | "vendor/assets/javascripts/algolia/v2", 57 | "vendor/assets/javascripts/algolia/v2/algoliasearch.angular.js", 58 | "vendor/assets/javascripts/algolia/v2/algoliasearch.angular.min.js", 59 | "vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.js", 60 | "vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.min.js", 61 | "vendor/assets/javascripts/algolia/v2/algoliasearch.js", 62 | "vendor/assets/javascripts/algolia/v2/algoliasearch.min.js", 63 | "vendor/assets/javascripts/algolia/v3", 64 | "vendor/assets/javascripts/algolia/v3/algoliasearch.angular.js", 65 | "vendor/assets/javascripts/algolia/v3/algoliasearch.angular.min.js", 66 | "vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.js", 67 | "vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.min.js", 68 | "vendor/assets/javascripts/algolia/v3/algoliasearch.js", 69 | "vendor/assets/javascripts/algolia/v3/algoliasearch.min.js" 70 | ] 71 | s.homepage = "http://github.com/algolia/algoliasearch-rails" 72 | s.licenses = ["MIT"] 73 | s.require_paths = ["lib"] 74 | s.rubygems_version = "2.1.11" 75 | s.summary = "AlgoliaSearch integration to your favorite ORM" 76 | 77 | if s.respond_to? :specification_version then 78 | s.specification_version = 4 79 | 80 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 81 | s.add_runtime_dependency(%q, [">= 1.5.1"]) 82 | s.add_runtime_dependency(%q, [">= 3.5.2"]) 83 | s.add_development_dependency(%q, [">= 2.3.15"]) 84 | s.add_development_dependency(%q, [">= 0"]) 85 | s.add_development_dependency(%q, [">= 0"]) 86 | s.add_development_dependency "rake" 87 | s.add_development_dependency "rdoc" 88 | else 89 | s.add_dependency(%q, [">= 1.5.1"]) 90 | s.add_dependency(%q, [">= 3.5.2"]) 91 | end 92 | else 93 | s.add_dependency(%q, [">= 1.5.1"]) 94 | s.add_dependency(%q, [">= 3.5.2"]) 95 | end 96 | end 97 | 98 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Hi there! We're thrilled that you'd like to contribute to this project. 4 | Your help is essential to keeping it great. 5 | 6 | ### Opening an issue 7 | 8 | Each repository provides a template for issues. Please tell us the client and language version, and 9 | provide a clear description of the problem you're facing. Steps to reproduce, or example code 10 | (repository, jsfiddle, and such), are a big help. 11 | 12 | ### Submitting a pull request 13 | 14 | Keep your changes as focused as possible. If there are multiple changes you'd like to make, 15 | please consider submitting them as separate pull requests unless they are related to each other. 16 | 17 | Here are a few tips to increase the likelihood of being merged: 18 | 19 | - [ ] Write tests. 20 | - [ ] Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 21 | - [ ] Allow [edits from maintainers](https://blog.github.com/2016-09-07-improving-collaboration-with-forks/). 22 | 23 | ### Security issues 24 | If you find any security risk in the project, please open an issue. 25 | 26 | ### API Breaking changes 27 | 28 | We care deeply about backward compatibility for our API clients libraries. 29 | If it's necessary, we're ready to break backward compatibility, 30 | but this should be pretty rare. 31 | 32 | If you want to make a change that will break the backward compatibility of the API, 33 | open an issue first to discuss it with the maintainers. 34 | 35 | ### Editing `README.md` and similar files 36 | 37 | Note that some files are managed outside this repository and are committed automatically. 38 | 39 | The `README.md` is generated automatically from our doc. If you'd like us to update this file, 40 | feel free to open an issue. 41 | 42 | `.github` directory is managed in [this repository](https://github.com/algolia/algoliasearch-client-common), 43 | any Pull Request there is welcome. 44 | 45 | ## Label caption 46 | 47 | Labels across all Algolia API clients repositories are normalized. 48 | 49 | 50 | 51 | | Label | Meaning | 52 | |---------------------------------------------------------------------------|----------------------------------------------------------------------------------------| 53 | | ![#050f2c](https://placehold.it/15/050f2c/000000?text=+) Do not merge | PR should not be merged (decided by maintainers) | 54 | | ![#ffc168](https://placehold.it/15/ffc168/000000?text=+) WIP | PR is not ready, no need to look at it (decided by contributors) | 55 | | ![#2ede98](https://placehold.it/15/2ede98/000000?text=+) Ready | The PR is ready, reviewed, tests are green, if you're brave enough: click merge button | 56 | | ![#ffc168](https://placehold.it/15/ffc168/000000?text=+) Waiting for API | The feature is implemented but the REST API is not live yet | 57 | | ![#3369e6](https://placehold.it/15/3369e6/000000?text=+) Discussion | We need everyone's opinion on this, please join the conversation and share your ideas | 58 | | ![#3369e6](https://placehold.it/15/3369e6/000000?text=+) Support | A user needs help but it's not really a bug | 59 | | ![#3369e6](https://placehold.it/15/3369e6/000000?text=+) API feature | New API feature added to every client (like query rules) | 60 | | ![#3369e6](https://placehold.it/15/3369e6/000000?text=+) Chore | CI, docs, and everything around the code | 61 | | ![#ff4f81](https://placehold.it/15/ff4f81/000000?text=+) Bug | It's a bug, fix it! | 62 | | ![#b60205](https://placehold.it/15/b60205/000000?text=+) Breaking change | RED ALERT! This means we need a new major version | 63 | | ![#ff6c5f](https://placehold.it/15/ff6c5f/000000?text=+) Good First Issue | If you want to contribute, this one is _easy_ to tackle! | 64 | 65 | 66 | 67 | 68 | ## Resources 69 | 70 | - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/) 71 | - [Using Pull Requests](https://help.github.com/articles/using-pull-requests/) 72 | - [GitHub Help](https://help.github.com) 73 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/algolia/bloodhound.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * typeahead.js 0.10.4 3 | * https://github.com/twitter/typeahead.js 4 | * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT 5 | */ 6 | 7 | !function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}}}(),c="0.10.4",d=function(){"use strict";function a(a){return a=b.toStr(a),a?a.split(/\s+/):[]}function c(a){return a=b.toStr(a),a?a.split(/\W+/):[]}function d(a){return function(){var c=[].slice.call(arguments,0);return function(d){var e=[];return b.each(c,function(c){e=e.concat(a(b.toStr(d[c])))}),e}}}return{nonword:c,whitespace:a,obj:{nonword:d(c),whitespace:d(a)}}}(),e=function(){"use strict";function c(c){this.maxSize=b.isNumber(c)?c:100,this.reset(),this.maxSize<=0&&(this.set=this.get=a.noop)}function d(){this.head=this.tail=null}function e(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(c.prototype,{set:function(a,b){var c,d=this.list.tail;this.size>=this.maxSize&&(this.list.remove(d),delete this.hash[d.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new e(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0},reset:function(){this.size=0,this.hash={},this.list=new d}}),b.mixin(d.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),c}(),f=function(){"use strict";function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+b.escapeRegExChars(this.prefix))}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),g=function(){"use strict";function c(b){b=b||{},this.cancelled=!1,this.lastUrl=null,this._send=b.transport?d(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get,this._cache=b.cache===!1?new e(0):i}function d(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new e(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i.reset()},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(null,b),k._cache.set(a,b)}function e(){c&&c(!0)}function i(){f--,delete g[a],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var j,k=this;this.cancelled||a!==this.lastUrl||((j=g[a])?j.done(d).fail(e):h>f?(f++,g[a]=this._send(a,b).done(d).fail(e).always(i)):this.onDeckRequestArgs=[].slice.call(arguments,0))},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),this.cancelled=!1,this.lastUrl=a,(e=this._cache.get(a))?b.defer(function(){d&&d(null,e)}):this._get(a,c,d),!!e},cancel:function(){this.cancelled=!0}}),c}(),h=function(){"use strict";function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.reset()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0,e=a.length;e>d;d++)b[a[d]]||(b[a[d]]=!0,c.push(a[d]));return c}function g(a,b){function c(a,b){return a-b}var d=0,e=0,f=[];a=a.sort(c),b=b.sort(c);for(var g=a.length,h=b.length;g>d&&h>e;)a[d]b[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},reset:function(){this.datums=[],this.trie=e()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),i=function(){"use strict";function d(a){return a.local||null}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,cache:!0,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}();!function(c){"use strict";function e(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=j(b.sorter),this.dupDetector=b.dupDetector||k,this.local=i.local(b),this.prefetch=i.prefetch(b),this.remote=i.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new h({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new f(this.cacheKey):null}function j(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function k(){return!1}var l,m;return l=c.Bloodhound,m={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.Bloodhound=e,e.noConflict=function(){return c.Bloodhound=l,e},e.tokenizers=d,b.mixin(e.prototype,{_loadPrefetch:function(b){function c(a){f.clear(),f.add(b.filter?b.filter(a):a),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a,c){b(a?[]:f.remote.filter?f.remote.filter(c):c)}var d,e,f=this;if(this.transport)return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_cancelLastRemoteRequest:function(){this.transport&&this.transport.cancel()},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(m.data,a,c),this.storage.set(m.protocol,location.protocol,c),this.storage.set(m.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(m.data),c.protocol=this.storage.get(m.protocol),c.thumbprint=this.storage.get(m.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},_initialize:function(){function c(){e.add(b.isFunction(f)?f():f)}var d,e=this,f=this.local;return d=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),f&&d.done(c),this.transport=this.remote?new g(this.remote):null,this.initPromise=d.promise()},initialize:function(a){return!this.initPromise||a?this._initialize():this.initPromise},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=f.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return e.dupDetector(a,b)}),!c&&d.push(a),d.length0||!this.transport)&&c&&c(f)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&g.resetCache()},ttAdapter:function(){return b.bind(this.get,this)}}),e}(this)}(window.jQuery); -------------------------------------------------------------------------------- /vendor/assets/javascripts/algolia/typeahead.jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * typeahead.js 0.10.4 3 | * https://github.com/twitter/typeahead.js 4 | * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT 5 | */ 6 | 7 | !function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}}}(),c=function(){return{wrapper:'',dropdown:'',dataset:'
',suggestions:'',suggestion:'
'}}(),d=function(){"use strict";var a={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none",opacity:"1"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};return b.isMsie()&&b.mixin(a.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),b.isMsie()&&b.isMsie()<=7&&b.mixin(a.input,{marginTop:"-1px"}),a}(),e=function(){"use strict";function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return b.mixin(c.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),c}(),f=function(){"use strict";function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0,f=a.length;!d&&f>e;e+=1)d=a[e].apply(b,c)===!1;return!d}return d}function g(){var a;return a=window.setImmediate?function(a){setImmediate(function(){a()})}:function(a){setTimeout(function(){a()},0)}}function h(a,b){return a.bind?a.bind(b):function(){a.apply(b,[].slice.call(arguments,0))}}var i=/\s+/,j=g();return{onSync:c,onAsync:b,off:d,trigger:e}}(),g=function(a){"use strict";function c(a,c,d){for(var e,f=[],g=0,h=a.length;h>g;g++)f.push(b.escapeRegExChars(a[g]));return e=d?"\\b("+f.join("|")+")\\b":"("+f.join("|")+")",c?new RegExp(e):new RegExp(e,"i")}var d={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(e){function f(b){var c,d,f;return(c=h.exec(b.data))&&(f=a.createElement(e.tagName),e.className&&(f.className=e.className),d=b.splitText(c.index),d.splitText(c[0].length),f.appendChild(d.cloneNode(!0)),b.parentNode.replaceChild(f,d)),!!c}function g(a,b){for(var c,d=3,e=0;e