├── .circleci └── config.yml ├── .dockerignore ├── .document ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── issue.yml ├── .gitignore ├── .rspec ├── CHANGELOG.MD ├── CONTRIBUTING.md ├── DOCKER_README.MD ├── Dockerfile ├── Gemfile ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── Rakefile ├── UPGRADING_TO_V3.MD ├── algoliasearch-rails.gemspec ├── lib ├── algoliasearch-rails.rb └── algoliasearch │ ├── algolia_job.rb │ ├── configuration.rb │ ├── pagination.rb │ ├── pagination │ ├── kaminari.rb │ ├── pagy.rb │ └── will_paginate.rb │ ├── railtie.rb │ ├── tasks │ └── algoliasearch.rake │ ├── utilities.rb │ └── version.rb ├── spec ├── integration_spec.rb ├── spec_helper.rb └── utilities_spec.rb └── vendor └── assets └── javascripts └── algolia ├── algoliasearch.angular.js ├── algoliasearch.angular.min.js ├── algoliasearch.jquery.js ├── algoliasearch.jquery.min.js ├── algoliasearch.js ├── algoliasearch.min.js ├── bloodhound.js ├── bloodhound.min.js ├── typeahead.bundle.js ├── typeahead.bundle.min.js ├── typeahead.jquery.js ├── typeahead.jquery.min.js ├── v2 ├── algoliasearch.angular.js ├── algoliasearch.angular.min.js ├── algoliasearch.jquery.js ├── algoliasearch.jquery.min.js ├── algoliasearch.js └── algoliasearch.min.js └── v3 ├── algoliasearch.angular.js ├── algoliasearch.angular.min.js ├── algoliasearch.jquery.js ├── algoliasearch.jquery.min.js ├── algoliasearch.js ├── algoliasearch.min.js ├── algoliasearch.parse.js ├── algoliasearchLite.js └── algoliasearchLite.min.js /.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'] -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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: 'DI-1911' 40 | }, 41 | project: { 42 | id: '10118' 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [Unreleased](https://github.com/algolia/algoliasearch-rails/compare/3.0.2...master) 4 | 5 | ## [3.0.2](https://github.com/algolia/algoliasearch-rails/compare/3.0.1...3.0.2) 6 | ### Fixed 7 | * Avoid re-initializing index [`#457`](https://github.com/algolia/algoliasearch-rails/pull/457) 8 | 9 | ## [3.0.1](https://github.com/algolia/algoliasearch-rails/compare/3.0.0...3.0.1) 10 | ### Fixed 11 | * Ensure init when `check_settings=true` for atomic reindexes [`#453`](https://github.com/algolia/algoliasearch-rails/pull/453) 12 | * Fix last task_id when no tasks to save [`#452`](https://github.com/algolia/algoliasearch-rails/pull/452) 13 | 14 | ## [3.0.0](https://github.com/algolia/algoliasearch-rails/compare/2.3.2...3.0.0) 15 | This new major version leverages the latest version of the Ruby Algolia API client. It also drops (official) support for Rails 5.x and Ruby versions older than 2.5 16 | For a list of known breaking changes, please refer to [the upgrade guide](./UPGRADING_TO_V3.MD) 17 | 18 | ## [2.3.2](https://github.com/algolia/algoliasearch-rails/compare/2.3.1...2.3.2) 19 | ### Added 20 | * Support for Pagy pagination provider [`#443`](https://github.com/algolia/algoliasearch-rails/pull/443) 21 | 22 | ## [2.3.1](https://github.com/algolia/algoliasearch-rails/compare/2.3.0...2.3.1) 23 | ### Added 24 | * Allow ActiveJob queue configuration [`#439`](https://github.com/algolia/algoliasearch-rails/pull/439) 25 | * allow to configure user agent and other default options [`#438`](https://github.com/algolia/algoliasearch-rails/pull/438) 26 | 27 | ## [2.3.0](https://github.com/algolia/algoliasearch-rails/compare/2.2.2...2.3.0) 28 | ### Fixed 29 | * bugfix: don't consider objectID changed when using custom ID through method [`#436`](https://github.com/algolia/algoliasearch-rails/pull/436) 30 | 31 | ## [2.2.2](https://github.com/algolia/algoliasearch-rails/compare/2.2.1...2.2.2) 32 | ### Fixed 33 | * fix: wrong method name [`#429`](https://github.com/algolia/algoliasearch-rails/pull/429) 34 | * fix: check if `Sequel::Model` is defined [`#433`](https://github.com/algolia/algoliasearch-rails/pull/433) 35 | 36 | ## [2.2.1](https://github.com/algolia/algoliasearch-rails/compare/2.2.0...2.2.1) 37 | ### Fixed 38 | * fix:settings changes detection issue with empty array [`#424`](https://github.com/algolia/algoliasearch-rails/pull/424) 39 | * fix: settings changes detection issue for replica indexes with inherit option [`#426`](https://github.com/algolia/algoliasearch-rails/pull/426) 40 | * fix: make sync opt to be selectable for auto setting update logic [`#425`](https://github.com/algolia/algoliasearch-rails/pull/425) 41 | 42 | ### Added 43 | * Add relevancyStrictness to IndexSettings [`#427`](https://github.com/algolia/algoliasearch-rails/pull/427) 44 | 45 | ## [2.2.0](https://github.com/algolia/algoliasearch-rails/compare/2.1.4...2.2.0) 46 | ### Added 47 | * Support to pass Algolia API client options [`#420`](https://github.com/algolia/algoliasearch-rails/pull/420) 48 | 49 | ### Fixed 50 | * Issue with `FrozenError` for newer Ruby versions [`#418`](https://github.com/algolia/algoliasearch-rails/pull/418) 51 | * Only check index settings when indexing is enabled [`#416`](https://github.com/algolia/algoliasearch-rails/pull/416) 52 | * Index intialization documentation [`#419`](https://github.com/algolia/algoliasearch-rails/pull/419) 53 | 54 | ## [2.1.4](https://github.com/algolia/algoliasearch-rails/compare/2.1.4...2.1.3) 55 | ### Fixed 56 | - File formatting [`#414`](https://github.com/algolia/algoliasearch-rails/pull/414) 57 | - Document supported Rails versions [`#407`](https://github.com/algolia/algoliasearch-rails/pull/407) 58 | - Fixed error when using `:check_settings => false` [`#413`](https://github.com/algolia/algoliasearch-rails/pull/413) 59 | 60 | ### Removed 61 | - Old references to TravisCI [`#408`](https://github.com/algolia/algoliasearch-rails/pull/408) 62 | 63 | ## [2.1.3](https://github.com/algolia/algoliasearch-rails/compare/2.1.3...2.1.2) 64 | ### Fixed 65 | - Prevent an extra get_settings call even if `check_settings` is false [`#367`](https://github.com/algolia/algoliasearch-rails/pull/367) 66 | 67 | ## [2.1.2](https://github.com/algolia/algoliasearch-rails/compare/2.1.2...2.1.1) 68 | ### Fixed 69 | - Error with keys that got symbolized by default [`#403`](https://github.com/algolia/algoliasearch-rails/pull/403) 70 | 71 | ## [2.1.1](https://github.com/algolia/algoliasearch-rails/compare/2.1.1...2.1.0) 72 | ### Fixed 73 | - Error with Ruby 3 in regards to default splat behavior [`#400`](https://github.com/algolia/algoliasearch-rails/pull/400) 74 | 75 | ## [2.1.0](https://github.com/algolia/algoliasearch-rails/compare/2.1.0...2.0.0) 76 | ### Added 77 | - Add support for [virtual replicas](https://www.algolia.com/doc/guides/managing-results/refine-results/sorting/in-depth/replicas/#what-are-virtual-replicas) ([#386](https://github.com/algolia/algoliasearch-rails/pull/386)) 78 | 79 | 80 | ## [2.0.0](https://github.com/algolia/algoliasearch-rails/compare/2.0.0...1.25.0) 81 | 82 | ### Breaking changes 83 | - Adds support for `algolia` gem. 84 | - Drops support for Ruby < 2.4 85 | - Drops support for Rails < 5.1 86 | 87 | ## [1.25.0](https://github.com/algolia/algoliasearch-rails/compare/1.24.1...1.25.0) (2020-11-24) 88 | 89 | ### Added 90 | - Pass array argument to geoloc ([#372](https://github.com/algolia/algoliasearch-rails/pull/372)) 91 | - Containerize the repo ([#391](https://github.com/algolia/algoliasearch-rails/pull/391)) 92 | 93 | ### Fixed 94 | - Use Zeitwerk for loading models in Rails 6 ([#364](https://github.com/algolia/algoliasearch-rails/pull/364)) 95 | 96 | ## [1.24.1](https://github.com/algolia/algoliasearch-rails/releases/tag/1.24.1) (2020-07-15) 97 | 98 | **Fixed** 99 | 100 | * Fixes ruby warning 'Proc.new is deprecated' 101 | 102 | ## [1.24.0](https://github.com/algolia/algoliasearch-rails/releases/tag/1.24.0) (2020-02-21) 103 | 104 | **Added** 105 | 106 | * Adds indexLanguages in index settings 107 | 108 | ## [1.23.2](https://github.com/algolia/algoliasearch-rails/releases/tag/1.23.2) (2019-06-26) 109 | 110 | **Fixed** 111 | 112 | * Use `copy_index` when initiating a temporary index to preserve settings, synonyms, and rules. 113 | 114 | 115 | ## [1.23.0](https://github.com/algolia/algoliasearch-rails/releases/tag/1.23.0) (2019-06-25) 116 | 117 | **Added** 118 | 119 | * Introduce `rake algoliasearch:set_all_settings` command - PR [#315](https://github.com/algolia/algoliasearch-rails/pull/315) 120 | 121 | This command will push settings for all models to all indices: primary index, 122 | replicas and additional indices. It follows the `inherit: true` option. 123 | It should typically be added to your deployment script 124 | 125 | * Add option to disable automatic settings - PR [#315](https://github.com/algolia/algoliasearch-rails/pull/315) 126 | 127 | By default, this gem check your settings to see when to push them. Depending on 128 | your implementation, it might create a lot of API calls. If you wish to disable 129 | the automatic change detection for settings, use the `check_settings` option: 130 | 131 | ```ruby 132 | class Musician < ActiveRecord::Base 133 | include AlgoliaSearch 134 | 135 | algoliasearch check_settings: false do 136 | # Settings... 137 | end 138 | end 139 | ``` 140 | 141 | **Fixed** 142 | 143 | * Handle attribute_changed? in transactions - PR [#354](https://github.com/algolia/algoliasearch-rails/pull/354) 144 | 145 | 146 | ## [1.22.0](https://github.com/algolia/algoliasearch-rails/releases/tag/1.22.0) (2019-03-21) 147 | 148 | 🚨 The documentation for our Rails integration was refreshed 🎉 149 | https://www.algolia.com/doc/framework-integration/rails/getting-started/setup/ 150 | 151 | **Added** 152 | 153 | * Introduce `algolia_dirty?` on models to decide if a model should be reindex. 154 | 155 | This feature already exists via _changed? methods but might requires to implements many 156 | methods if you have multiple dynamic attributes. Dynamic attributes are attributes not mapping 157 | to a DB column. This feature allows you to group avoid all the _changed? method calls and 158 | group all the logic inside one unique method. 159 | 160 | **Fixed** 161 | 162 | * Fix _changed? method call for Rails 5.2+ with dynamic attribute - PR [#338](https://github.com/algolia/algoliasearch-rails/pull/338) 163 | 164 | Related issue: https://github.com/algolia/algoliasearch-rails/issues/140 165 | Documentation: https://www.algolia.com/doc/framework-integration/rails/indexing/indexing/#automatic-updates 166 | 167 | 168 | ## [1.21.0](https://github.com/algolia/algoliasearch-rails/releases/tag/1.21.0) (2019-02-12) 169 | 170 | **Added** 171 | 172 | * Add `AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE` constant - PR [#319](https://github.com/algolia/algoliasearch-rails/pull/319) 173 | 174 | * Support ActiveModel Serializer 🎉 - PR [#266](https://github.com/algolia/algoliasearch-rails/pull/266) 175 | 176 | ```ruby 177 | class SerializedObject < ActiveRecord::Base 178 | include AlgoliaSearch 179 | 180 | algoliasearch do 181 | 182 | use_serializer SerializedObjectSerializer 183 | 184 | tags do 185 | ['tag1', 'tag2'] 186 | end 187 | 188 | end 189 | end 190 | ``` 191 | 192 | **Fixed** 193 | 194 | * Add support for all new recent engine settings - PR [#327](https://github.com/algolia/algoliasearch-rails/pull/327) 195 | 196 | Algolia regularly introduce new settings, all of them are supported by this gem. 197 | 198 | 2018-12-07 1.20.6 199 | 200 | * Better support for Rails 5.1 and deprecated `attribute_changed?` 201 | * Better support for `after_commit` for Sequel 5.0 202 | 203 | 2017-12-04 1.20.4 204 | 205 | * Remove Bundler and RubyGems requirements 206 | 207 | 2017-11-02 1.20.2 208 | 209 | * Upgrade algoliasearch gem to 1.17.0 210 | 211 | 2017-08-04 1.20.1 212 | 213 | * Make sure objectIDs are manipulated as strings 214 | 215 | 2017-07-31 1.20.0 216 | 217 | * Override the user-agent (#238) 218 | * Fixed NPE while running mocked tests (#242) 219 | * Add `paginationLimitedTo` setting (#243) 220 | * Make `algolia_without_auto_index_scope` thread-safe (#234) 221 | 222 | 2017-03-13 1.19.1 223 | 224 | * Make sure get_model_classes is not ActiveRecord compliant only 225 | 226 | 2017-03-13 1.19.0 227 | 228 | * Rewrote the algoliasearch:reindex rake task to better detect models to reindex 229 | * Fixed the handling of `raise_on_failture` for class methods 230 | 231 | 2017-02-16 1.18.0 232 | 233 | * Add new replica parameter, `inherit` (#198) 234 | 235 | 2017-01-05 1.17.1 236 | 237 | * Add missing `disablePrefixOnAttributes` and `disableTypoToleranceOnAttributes` attributes (#193) 238 | 239 | 2017-01-05 1.17.0 240 | 241 | * Do not enqueue indexing operations if the indexing is disabled 242 | * Add `searchableAttributes` and `numericAttributesForFiltering` index settings 243 | * Fixed here and there synchronous behaviors (mostly unit tests related) 244 | * Rename `slave` to `replica` 245 | * Upgraded `algoliasearch-client-{ruby,js}` deps 246 | 247 | 2016-11-25 1.16.3 248 | 249 | * Propagate `search_facet` to `search_for_facet_values` renaming (backward compatible) 250 | * Upgrade `algoliasearch` dependency to ~> 1.12.1 251 | * Add missing `typoTolerance` setting 252 | 253 | 2016-11-14 1.16.2 254 | 255 | * Add missing disableTypoToleranceOnAttributes and disableTypoToleranceOnWords index settings 256 | 257 | 2016-11-04 1.16.1 258 | 259 | * Added IndexSettings#get_attribute_names 260 | * Refactored IndexSettings#get_attributes 261 | * algolia_must_reindex? doesn't compute attribute values anymore 262 | 263 | 2016-10-02 1.16.0 264 | 265 | * Upgrade `algoliasearch` to 1.12.0 266 | * Add `search_facet` method 267 | 268 | 2016-10-02 1.15.1 269 | 270 | * Missing `advancedSyntax` index setting forward 271 | 272 | 2016-08-21 1.15.0 273 | 274 | * Upgrade to AlgoliaSearch JS API client 3.18.0 275 | * Upgrade to AlgoliaSearch Ruby API client 1.11.0 276 | * Official support of Rails 5.x 277 | * Reduce 404 logging if get_settings on a non-existing index 278 | * Here and there fixes 279 | 280 | 2015-08-19 1.14.1 281 | 282 | * Upgrade to AlgoliaSearch JS API client 3.7.5 283 | 284 | 2015-08-11 1.14.0 285 | 286 | * Add numericAttributesToIndex index setting 287 | 288 | 2015-08-07 1.13.4 289 | 290 | * Do not consider the per_environment option while initializing the temporary index. 291 | 292 | 2015-08-05 1.13.3 293 | 294 | * The `active_job.rb` file was not included in the gem 295 | 296 | 2015-08-01 1.13.2 297 | 298 | * Lazy load the `ActiveJob` class to ensure the underlying queuing system is initialized when we actually use it. 299 | 300 | 2015-07-20 1.13.1 301 | 302 | * Use `after_commit` instead of `after_save` to ensure we index once in the DB 303 | 304 | 2015-06-21 1.13.0 305 | 306 | * Ability to use a background queue to handle all automatic indexing operations 307 | * Fix a bug avoiding the gem to remove records when using the Sequel ORM 308 | 309 | 2015-05-28 1.12.2 310 | 311 | * Add a new `raise_on_failure` option, controlling whereas exceptions are raised while trying to reach Algolia's API. 312 | 313 | 2015-05-12 1.12.1 314 | 315 | * Upgrade to algoliasearch-client-js 3.3.0 316 | 317 | 2015-04-29 1.12.0 318 | 319 | * Add Sequel support. 320 | 321 | 2015-03-31 1.11.18 322 | 323 | * Embed AlgoliaSearch JS API client v3 as well. Default is still the v2 to keep the backward compatibility. 324 | 325 | 2015-03-08 1.11.17 326 | 327 | * Add missing index settings (including removeWordsIfNoResults, unretrievableAttributes and ignorePlurals) 328 | 329 | 2015-02-23 1.11.16 330 | 331 | * Include jQuery & Angular related builds in the gem 332 | 333 | 2015-02-23 1.11.15 334 | 335 | * Algoliasearch-client-js: embed jQuery & angular based builds as well 336 | 337 | 2015-02-23 1.11.14 338 | 339 | * Upgrade to algoliasearch-client-js 2.9.2 340 | 341 | 2015-02-03 1.11.13 342 | 343 | * Upgrade to algoliasearch-client-js 2.8.6 344 | 345 | 2015-01-29 1.11.12 346 | 347 | * Fixed potential namespace issue while trying to load the HTML sanitizer 348 | 349 | 2014-11-27 1.11.11 350 | 351 | * Upgraded algoliasearch JavaScript client to 2.7.5 to prepare TLD migration 352 | 353 | 2014-11-21 1.11.10 354 | 355 | * Ability to force the UTF-8 encoding of the underlying attributes before sending them to our API 356 | 357 | 2014-11-12 1.11.9 358 | 359 | * Upgraded algoliasearch JavaScript client to 2.7.4 & algoliasearch-client-ruby to 1.2.14 360 | 361 | 2014-10-29 1.11.8 362 | 363 | * Upgraded algoliasearch JavaScript client to 2.7.3 364 | 365 | 2014-10-16 1.11.7 366 | 367 | * Upgraded to algoliasearch-client-ruby>=1.2.12 to ensure we're not using SSLv3 368 | 369 | 2014-10-15 1.11.6 370 | 371 | * Upgraded algoliasearch JavaScript client to 2.7.1 372 | 373 | 2014-10-15 1.11.5 374 | 375 | * Upgraded algoliasearch JavaScript client from 2.5.3 to 2.7.0 376 | 377 | 2014-10-05 1.11.4 378 | 379 | * Do not compute the 'slaves' index settings on slaves (breaking the diff between current & configured version) 380 | 381 | 2014-09-16 1.11.3 382 | 383 | * While reindexing, fetch the master's settings to setup the temporary index 384 | 385 | 2014-09-02 1.11.2 386 | 387 | * Ability to search in a slave or extra index using the ```:index```/```:slave``` parameter 388 | 389 | 2014-08-25 1.11.1 390 | 391 | * While using a temporary index to reindex, do not set the "slaves" index setting (will be set at move-time) 392 | * Ability to strip HTML tags from attributes 393 | 394 | 2014-08-20 1.11.0 395 | 396 | * Do not rely of _changed? method to detect reindexing needs, if those methods are missing we probably need to reindex 397 | 398 | 2014-08-07 1.10.9 399 | 400 | * Upgrade to algoliasearch-client-js 2.5.3 401 | * Upgrade to typeahead 1.10.4 402 | 403 | 2014-08-07 1.10.8 404 | 405 | * Fixes searches on Mongoid introduced in 1.10.7 (author: @zarqman) 406 | 407 | 2014-07-17 1.10.7 408 | 409 | * Query optimization: load search results from database using a single query (author: @outoftime) 410 | 411 | 2014-07-10 1.10.6 412 | 413 | * Pass the configuration hash to the underlying ```Algolia.init``` method. 414 | 415 | 2014-07-09 1.10.5 416 | 417 | * Safely reindex your data using ```MyModel.reindex``` (index with a temporary index + move), ```MyModel.reindex!``` do it in-place without removing out-dated records 418 | 419 | 2014-06-28 1.10.4 420 | 421 | * Ability to disable all indexing tasks (testing purpose) 422 | 423 | 2014-06-17 1.10.3 424 | 425 | * Pagination: fixed a padding issue with recent versions of Kaminari 426 | 427 | 2014-05-09 1.10.2 428 | 429 | * Expose synoyms/placeholders features 430 | * Upgrade to algoliasearch-client-js 2.5.0 (fallback on JSONP if CORS is not available) 431 | 432 | 2014-04-29 1.10.1 433 | 434 | * Use :if and :unless constraints to detect if a record has changed as well 435 | 436 | 2014-04-28 1.10.0 437 | 438 | * Ability to configure slave indexes from the ```algoliasearch``` block 439 | * Ability to target multiple indexes from a single model class 440 | 441 | 2014-04-23 1.9.5 442 | 443 | * Add missing highlightPreTag/highlightPostTag settings 444 | 445 | 2014-03-30 1.9.4 446 | 447 | * Ability to index an array of objects using the `index_objects` method. Ref #15 448 | * Upgrade typeahead.js to 0.10.2 (fixed flickering) 449 | * Upgrade algoliasearch-client-ruby to 1.2.8 (fixed unhandled exception) 450 | * Upgrade algoliasearch-client-js to 2.4.5 (embed last ExplainResults helper fixes) 451 | 452 | 2014-03-26 1.9.3 453 | 454 | * Ruby 1.8 compatibility 455 | * Fixed a bug ignoring hitsPerPage parameter while using backend pagination (will_paginate/kaminari) 456 | 457 | 2014-03-17 1.9.2 458 | 459 | * Raise an exception while attempting to index a non-saved object (blank objectID) 460 | 461 | 2014-03-06 1.9.1 462 | 463 | * When using MongoId, the `reindex!` method was calling add_objects too many times. (credits go to sush.io) 464 | 465 | 2014-02-24 1.9.0 466 | 467 | * Upgrade to algoliasearch 1.2.5 and algoliasearch-client-js 2.4.2 468 | * Fixed pagination starting at 1 in Kaminari & WillPaginate and 0 in Algolia 469 | * As soon as :if or :unless are used, we need to remove the objects from the index 470 | if the constraints don't match 471 | 472 | 2014-02-15 1.8.2 473 | 474 | * Ability to retrieve facets and raw answer as well 475 | 476 | 2014-02-14 1.8.1 477 | 478 | * Fixed backend pagination not taking current page into account at display time. 479 | 480 | 2014-02-12 1.8.0 481 | 482 | * Upgrade to official Twitter Typeahead.js 0.10.1 (/!\ API has changed) 483 | * Fixed reindexing rake task (#12) 484 | 485 | 2014-02-04 1.7.2 486 | 487 | * Add a ```raw_search``` method, retrieving the JSON raw answer 488 | * Support STI subclasses (#11) 489 | * Updated dependencies 490 | 491 | 2014-01-31 1.7.1 492 | 493 | * Ensure methods are not conflicting with already defined ones (use algolia_METHOD_NAME) 494 | * Add ```:if``` and ```:unless``` options to control objects indexing (#10) 495 | 496 | 2014-01-07 1.7.0 497 | 498 | * Updated algoliasearch to 1.2 (httpclient instead of curb as underlying HTTP layer) 499 | * jruby, rbx and ruby 2.1 compatibility 500 | 501 | 2014-01-02 1.6.3 502 | 503 | * Expose new ```attributeForDistinct``` setting 504 | 505 | 2013-12-17 1.6.2 506 | 507 | * Updated deps (included algoliasearch 1.1.15 and algoliasearch-client-js 2.3.6) 508 | 509 | 2013-12-05 1.6.1 510 | 511 | * ```add_attribute``` can now be used to add extra attributes in addition to the existing ones 512 | * Use the ```geoloc``` and ```tags``` methods to generate the associated ```_tags``` and ```_geoloc``` attributes 513 | 514 | 2013-12-05 1.5.2 515 | 516 | * object's attributes must be fetched unscoped to ensure associations will not be impacted by the conditions 517 | * you can now use `Model.index` to access the underlying index object 518 | 519 | 2013-12-03 1.5.1 520 | 521 | * ability to specify which attribute is used as objectID 522 | 523 | 2013-11-29 1.4.5 524 | 525 | * updated algoliasearch-client-js to 2.3.3 526 | 527 | 2013-11-29 1.4.4 528 | 529 | * updated algoliasearch to 1.1.11 530 | 531 | 2013-11-29 1.4.3 532 | 533 | * updated algoliasearch-client-js to 2.3.2 534 | * updated algoliasearch to 1.1.10 535 | 536 | 2013-11-25 1.4.2 537 | 538 | * fixed const_get calls with ruby 1.9.3 539 | 540 | 2013-11-25 1.4.1 541 | 542 | * ability to specify a block associated to an attribute as value 543 | 544 | 2013-11-22 1.3.10 545 | 546 | * updated algoliasearch.js (2.3.2) 547 | 548 | 2013-11-21 1.3.9 549 | 550 | * updated custom typeahead.js (merged https://github.com/twitter/typeahead.js/pull/390) 551 | 552 | 2013-11-21 1.3.8 553 | 554 | * expose new index settings: separatorsToIndex and optionalWords 555 | 556 | 2013-11-19 1.3.7 557 | 558 | * handle namespaced models (s/::/_/) 559 | 560 | 2013-11-08 1.3.6 561 | 562 | * upgraded to algoliasearch 1.1.6 (array-based search parameters were not encoded) 563 | 564 | 2013-11-07 1.3.5 565 | 566 | * add without_auto_index_scope method disabling all indexing operations inside associated block 567 | 568 | 2013-11-07 1.3.4 569 | 570 | * updated algoliasearch to 1.1.6 (clear_index! is now a real clear, not a delete/create) and algoliasearch-client-js to 2.3.0 571 | * restore missing ensure_init call in the ```search``` method 572 | 573 | 2013-11-07 1.3.3 574 | 575 | * new :per_environment options suffixing index's name by the Rails environment 576 | * index's settings are now initialized on first usage, not at include time 577 | * plug attributesForFaceting setting 578 | 579 | 2013-10-17 1.3.2 580 | 581 | * while detecting settings changes, fixed array comparison 582 | 583 | 2013-10-17 1.3.1 584 | 585 | * upgraded to algoliasearch 1.1.4 (too aggressive wait_task calls) 586 | 587 | 2013-10-15 1.3.0 588 | 589 | * synchronous flag is now false by default 590 | * do not set settings if they didn't chang 591 | 592 | 2013-10-15 1.2.1 593 | 594 | * Updated deps, especially algoliasearch to 1.1.3 to solve thread-safety issues 595 | 596 | 2013-10-14 1.2.0 597 | 598 | * embeds a typeahead.js based UI 599 | 600 | 2013-10-04 1.1.8 601 | 602 | * Avoid concurrent access to the same index while running tests with TravisCI 603 | * to check if the full-reindexing has been done, checking the last task is enough 604 | 605 | 2013-10-02 1.1.7 606 | 607 | * Updated environment variables 608 | * Plug travis and various badges 609 | * Fixed attribute changes detection 610 | * Upgrade to algoliasearch-client-ruby 1.1.1 611 | 612 | 2013-10-01 1.1.6 613 | 614 | * Initial import 615 | 616 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/version.rb: -------------------------------------------------------------------------------- 1 | module AlgoliaSearch 2 | VERSION = '3.0.2' 3 | end 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/algolia/algoliasearch.angular.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * algoliasearch 2.9.7 3 | * https://github.com/algolia/algoliasearch-client-js 4 | * Copyright 2014 Algolia SAS; Licensed MIT 5 | */ 6 | 7 | function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i0))return this._sendQueriesBatch(d,a);var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f},setRequestTimeout:function(a){a&&(this.requestTimeoutInMs=parseInt(a,10))},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null===this.jsonp){var c=this;return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:function(d,e){d?(c.jsonp=!1,b&&b(d,e)):(c.jsonp=!0,c._sendQueriesBatch(a,b))}})}if(this.jsonp){for(var d="",e=0;e=b.hosts.length){var h={message:"Cannot connect the Algolia's Search API. Please send an email to support@algolia.com to report the issue."};return!b._isUndefined(c)&&c&&(a.successiveRetryCount=0,c(!1,h)),void(f&&f.reject(h))}a.callback=function(h,i,j){i&&!b._isUndefined(a.cache)&&(d[e]=j),!i&&h?(b.currentHostIndex=++b.currentHostIndex%b.hosts.length,a.successiveRetryCount+=1,g()):(a.successiveRetryCount=0,f&&(i?f.resolve(j):f.reject(j)),!b._isUndefined(c)&&c&&c(i,j))},a.hostname=b.hosts[b.currentHostIndex],b._jsonRequestByHost(a)};return g(),f&&f.promise},_jsonRequestByHost:function(a){var b=a.hostname+a.url;this.jsonp?this._makeJsonpRequestByHost(b,a):this.options.jQuery?this._makejQueryRequestByHost(b,a):this.options.angular?this._makeAngularRequestByHost(b,a):this._makeXmlHttpRequestByHost(b,a)},_makeAngularRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters)),a+="&X-Algolia-Agent="+encodeURIComponent(this._ua);for(var d=0;d0))return this._search(f,b);var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g},browse:function(a,b,c){+b>0&&(this.as._isUndefined(c)||!c)&&(c=b,b=null);var d=this,e="?page="+a;return this.as._isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){d(a?b.hits:b&&b.message)},a)}},waitTask:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};return d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){var c={params:a};if(null===this.as.jsonp){var d=this;return this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:function(c,e){var f=e&&e.status;c||f&&4===Math.floor(f/100)||1===Math.floor(f/100)?(d.as.jsonp=!1,b&&b(c,e)):(d.as.jsonp=!0,d._search(a,b))}})}return this.as.jsonp?this.as._jsonRequest({cache:this.cache,method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:c,callback:b}):this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:b})},as:null,indexName:null,typeAheadArgs:null,typeAheadValueOption:null},function(a){var b=function(a){a=a||{};for(var b=1;b0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},setIndex:function(a){this.index=a},getIndex:function(){return this.index},clearExtraQueries:function(){this.extraQueries=[]},addExtraQuery:function(a,b,c){this.extraQueries.push({index:a,query:b,params:c||{}})},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());var a=[],b={},c=0;for(c=0;c0&&b.push(f)}return b}}}(),function(a){window.AlgoliaPlaces=function(a,b){this.init(a,b)},AlgoliaPlaces.prototype={init:function(a,b){this.client=new AlgoliaSearch(a,b,"http",!0,["places-1.algolia.io","places-2.algolia.io","places-3.algolia.io"]),this.cache={}},search:function(a,b,c){var d="query="+encodeURIComponent(a);this.client._isUndefined(c)||null==c||(d=this.client._getSearchParams(c,d));var e={params:d,apiKey:this.client.apiKey,appID:this.client.applicationID};this.client._jsonRequest({cache:this.cache,method:"POST",url:"/1/places/query",body:e,callback:b,removeCustomHTTPHeaders:!0})}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),angular.module("algoliasearch",[]).service("algolia",["$injector",function(a){return{Client:function(b,c,d){return d=d||{},d.angular={$injector:a},d._ua="Algolia for AngularJS "+window.ALGOLIA_VERSION,new AlgoliaSearch(b,c,d)}}}]); -------------------------------------------------------------------------------- /vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * algoliasearch 2.9.7 3 | * https://github.com/algolia/algoliasearch-client-js 4 | * Copyright 2014 Algolia SAS; Licensed MIT 5 | */ 6 | 7 | function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i0))return this._sendQueriesBatch(d,a);var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f},setRequestTimeout:function(a){a&&(this.requestTimeoutInMs=parseInt(a,10))},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null===this.jsonp){var c=this;return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:function(d,e){d?(c.jsonp=!1,b&&b(d,e)):(c.jsonp=!0,c._sendQueriesBatch(a,b))}})}if(this.jsonp){for(var d="",e=0;e=b.hosts.length){var h={message:"Cannot connect the Algolia's Search API. Please send an email to support@algolia.com to report the issue."};return!b._isUndefined(c)&&c&&(a.successiveRetryCount=0,c(!1,h)),void(f&&f.reject(h))}a.callback=function(h,i,j){i&&!b._isUndefined(a.cache)&&(d[e]=j),!i&&h?(b.currentHostIndex=++b.currentHostIndex%b.hosts.length,a.successiveRetryCount+=1,g()):(a.successiveRetryCount=0,f&&(i?f.resolve(j):f.reject(j)),!b._isUndefined(c)&&c&&c(i,j))},a.hostname=b.hosts[b.currentHostIndex],b._jsonRequestByHost(a)};return g(),f&&f.promise},_jsonRequestByHost:function(a){var b=a.hostname+a.url;this.jsonp?this._makeJsonpRequestByHost(b,a):this.options.jQuery?this._makejQueryRequestByHost(b,a):this.options.angular?this._makeAngularRequestByHost(b,a):this._makeXmlHttpRequestByHost(b,a)},_makeAngularRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters)),a+="&X-Algolia-Agent="+encodeURIComponent(this._ua);for(var d=0;d0))return this._search(f,b);var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g},browse:function(a,b,c){+b>0&&(this.as._isUndefined(c)||!c)&&(c=b,b=null);var d=this,e="?page="+a;return this.as._isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){d(a?b.hits:b&&b.message)},a)}},waitTask:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};return d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){var c={params:a};if(null===this.as.jsonp){var d=this;return this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:function(c,e){var f=e&&e.status;c||f&&4===Math.floor(f/100)||1===Math.floor(f/100)?(d.as.jsonp=!1,b&&b(c,e)):(d.as.jsonp=!0,d._search(a,b))}})}return this.as.jsonp?this.as._jsonRequest({cache:this.cache,method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:c,callback:b}):this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:b})},as:null,indexName:null,typeAheadArgs:null,typeAheadValueOption:null},function(a){var b=function(a){a=a||{};for(var b=1;b0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},setIndex:function(a){this.index=a},getIndex:function(){return this.index},clearExtraQueries:function(){this.extraQueries=[]},addExtraQuery:function(a,b,c){this.extraQueries.push({index:a,query:b,params:c||{}})},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());var a=[],b={},c=0;for(c=0;c0&&b.push(f)}return b}}}(),function(a){window.AlgoliaPlaces=function(a,b){this.init(a,b)},AlgoliaPlaces.prototype={init:function(a,b){this.client=new AlgoliaSearch(a,b,"http",!0,["places-1.algolia.io","places-2.algolia.io","places-3.algolia.io"]),this.cache={}},search:function(a,b,c){var d="query="+encodeURIComponent(a);this.client._isUndefined(c)||null==c||(d=this.client._getSearchParams(c,d));var e={params:d,apiKey:this.client.apiKey,appID:this.client.applicationID};this.client._jsonRequest({cache:this.cache,method:"POST",url:"/1/places/query",body:e,callback:b,removeCustomHTTPHeaders:!0})}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),function(a){a.algolia={},a.algolia.Client=function(b,c,d){return d=d||{},d.jQuery={$:a},d._ua="Algolia for jQuery "+window.ALGOLIA_VERSION,new AlgoliaSearch(b,c,d)}}(jQuery); -------------------------------------------------------------------------------- /vendor/assets/javascripts/algolia/algoliasearch.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * algoliasearch 2.9.7 3 | * https://github.com/algolia/algoliasearch-client-js 4 | * Copyright 2014 Algolia SAS; Licensed MIT 5 | */ 6 | 7 | function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i0))return this._sendQueriesBatch(d,a);var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f},setRequestTimeout:function(a){a&&(this.requestTimeoutInMs=parseInt(a,10))},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null===this.jsonp){var c=this;return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:function(d,e){d?(c.jsonp=!1,b&&b(d,e)):(c.jsonp=!0,c._sendQueriesBatch(a,b))}})}if(this.jsonp){for(var d="",e=0;e=b.hosts.length){var h={message:"Cannot connect the Algolia's Search API. Please send an email to support@algolia.com to report the issue."};return!b._isUndefined(c)&&c&&(a.successiveRetryCount=0,c(!1,h)),void(f&&f.reject(h))}a.callback=function(h,i,j){i&&!b._isUndefined(a.cache)&&(d[e]=j),!i&&h?(b.currentHostIndex=++b.currentHostIndex%b.hosts.length,a.successiveRetryCount+=1,g()):(a.successiveRetryCount=0,f&&(i?f.resolve(j):f.reject(j)),!b._isUndefined(c)&&c&&c(i,j))},a.hostname=b.hosts[b.currentHostIndex],b._jsonRequestByHost(a)};return g(),f&&f.promise},_jsonRequestByHost:function(a){var b=a.hostname+a.url;this.jsonp?this._makeJsonpRequestByHost(b,a):this.options.jQuery?this._makejQueryRequestByHost(b,a):this.options.angular?this._makeAngularRequestByHost(b,a):this._makeXmlHttpRequestByHost(b,a)},_makeAngularRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters)),a+="&X-Algolia-Agent="+encodeURIComponent(this._ua);for(var d=0;d0))return this._search(f,b);var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g},browse:function(a,b,c){+b>0&&(this.as._isUndefined(c)||!c)&&(c=b,b=null);var d=this,e="?page="+a;return this.as._isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){d(a?b.hits:b&&b.message)},a)}},waitTask:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};return d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){var c={params:a};if(null===this.as.jsonp){var d=this;return this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:function(c,e){var f=e&&e.status;c||f&&4===Math.floor(f/100)||1===Math.floor(f/100)?(d.as.jsonp=!1,b&&b(c,e)):(d.as.jsonp=!0,d._search(a,b))}})}return this.as.jsonp?this.as._jsonRequest({cache:this.cache,method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:c,callback:b}):this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:b})},as:null,indexName:null,typeAheadArgs:null,typeAheadValueOption:null},function(a){var b=function(a){a=a||{};for(var b=1;b0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},setIndex:function(a){this.index=a},getIndex:function(){return this.index},clearExtraQueries:function(){this.extraQueries=[]},addExtraQuery:function(a,b,c){this.extraQueries.push({index:a,query:b,params:c||{}})},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());var a=[],b={},c=0;for(c=0;c0&&b.push(f)}return b}}}(),function(a){window.AlgoliaPlaces=function(a,b){this.init(a,b)},AlgoliaPlaces.prototype={init:function(a,b){this.client=new AlgoliaSearch(a,b,"http",!0,["places-1.algolia.io","places-2.algolia.io","places-3.algolia.io"]),this.cache={}},search:function(a,b,c){var d="query="+encodeURIComponent(a);this.client._isUndefined(c)||null==c||(d=this.client._getSearchParams(c,d));var e={params:d,apiKey:this.client.apiKey,appID:this.client.applicationID};this.client._jsonRequest({cache:this.cache,method:"POST",url:"/1/places/query",body:e,callback:b,removeCustomHTTPHeaders:!0})}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(); -------------------------------------------------------------------------------- /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