{"id":13462945,"url":"https://github.com/karmi/retire","last_synced_at":"2025-12-18T10:08:22.711Z","repository":{"id":1382767,"uuid":"1337406","full_name":"karmi/retire","owner":"karmi","description":"A rich Ruby API and DSL for the Elasticsearch search engine","archived":true,"fork":false,"pushed_at":"2018-04-18T18:50:17.000Z","size":4994,"stargazers_count":1867,"open_issues_count":96,"forks_count":534,"subscribers_count":52,"default_branch":"master","last_synced_at":"2024-05-20T20:44:51.005Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://karmi.github.com/retire/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karmi.png","metadata":{"files":{"readme":"README.markdown","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-02-07T11:32:05.000Z","updated_at":"2024-04-21T13:22:01.000Z","dependencies_parsed_at":"2022-07-07T10:53:50.639Z","dependency_job_id":null,"html_url":"https://github.com/karmi/retire","commit_stats":null,"previous_names":["karmi/tire"],"tags_count":61,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmi%2Fretire","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmi%2Fretire/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmi%2Fretire/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karmi%2Fretire/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karmi","download_url":"https://codeload.github.com/karmi/retire/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219867930,"owners_count":16554383,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T13:00:41.640Z","updated_at":"2025-09-28T20:30:24.784Z","avatar_url":"https://github.com/karmi.png","language":"Ruby","funding_links":[],"categories":["Active Record Plugins","Ruby"],"sub_categories":["Rails Search"],"readme":"Tire\n=========\n\n---------------------------------------------------------------------------------------------------\n\n  NOTICE: This library has been renamed and retired in September 2013\n          ([read the explanation](https://github.com/karmi/retire/wiki/Tire-Retire)).\n          It is not considered compatible with Elasticsearch 1.x.\n\n  Have a look at the **\u003chttp://github.com/elasticsearch/elasticsearch-rails\u003e** suite of gems,\n  which contain similar set of features for ActiveModel/Record and Rails integration as Tire.\n\n---------------------------------------------------------------------------------------------------\n\n_Tire_ is a Ruby (1.8 or 1.9) client for the [Elasticsearch](http://www.elasticsearch.org/)\nsearch engine/database.\n\n_Elasticsearch_ is a scalable, distributed, cloud-ready, highly-available,\nfull-text search engine and database with\n[powerful aggregation features](http://www.elasticsearch.org/guide/reference/api/search/facets/),\ncommunicating by JSON over RESTful HTTP, based on [Lucene](http://lucene.apache.org/), written in Java.\n\nThis Readme provides a brief overview of _Tire's_ features. The more detailed documentation is at \u003chttp://karmi.github.com/retire/\u003e.\n\nBoth of these documents contain a lot of information. Please set aside some time to read them thoroughly, before you blindly dive into „somehow making it work“. Just skimming through it **won't work** for you. For more information, please see the project [Wiki](https://github.com/karmi/tire/wiki/_pages), search the [issues](https://github.com/karmi/tire/issues), and refer to the [integration test suite](https://github.com/karmi/tire/tree/master/test/integration).\n\nInstallation\n------------\n\nOK. First, you need a running _Elasticsearch_ server. Thankfully, it's easy. Let's define easy:\n\n    $ curl -k -L -o elasticsearch-0.20.6.tar.gz http://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.20.6.tar.gz\n    $ tar -zxvf elasticsearch-0.20.6.tar.gz\n    $ ./elasticsearch-0.20.6/bin/elasticsearch -f\n\nSee, easy. On a Mac, you can also use _Homebrew_:\n\n    $ brew install elasticsearch\n\nNow, let's install the gem via Rubygems:\n\n    $ gem install tire\n\nOf course, you can install it from the source as well:\n\n    $ git clone git://github.com/karmi/tire.git\n    $ cd tire\n    $ rake install\n\n\nUsage\n-----\n\n_Tire_ exposes easy-to-use domain specific language for fluent communication with _Elasticsearch_.\n\nIt easily blends with your _ActiveModel_/_ActiveRecord_ classes for convenient usage in _Rails_ applications.\n\nTo test-drive the core _Elasticsearch_ functionality, let's require the gem:\n\n```ruby\n    require 'rubygems'\n    require 'tire'\n```\n\nPlease note that you can copy these snippets from the much more extensive and heavily annotated file\nin [examples/tire-dsl.rb](http://karmi.github.com/retire/).\n\nAlso, note that we're doing some heavy JSON lifting here. _Tire_ uses the\n[_multi_json_](https://github.com/intridea/multi_json) gem as a generic JSON wrapper,\nwhich allows you to use your preferred JSON library. We'll use the\n[_yajl-ruby_](https://github.com/brianmario/yajl-ruby) gem in the full on mode here:\n\n```ruby\n    require 'yajl/json_gem'\n```\n\nLet's create an index named `articles` and store/index some documents:\n\n```ruby\n    Tire.index 'articles' do\n      delete\n      create\n\n      store :title =\u003e 'One',   :tags =\u003e ['ruby']\n      store :title =\u003e 'Two',   :tags =\u003e ['ruby', 'python']\n      store :title =\u003e 'Three', :tags =\u003e ['java']\n      store :title =\u003e 'Four',  :tags =\u003e ['ruby', 'php']\n\n      refresh\n    end\n```\n\nWe can also create the index with custom\n[mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html)\nfor a specific document type:\n\n```ruby\n    Tire.index 'articles' do\n      delete\n\n      create :mappings =\u003e {\n        :article =\u003e {\n          :properties =\u003e {\n            :id       =\u003e { :type =\u003e 'string', :index =\u003e 'not_analyzed', :include_in_all =\u003e false },\n            :title    =\u003e { :type =\u003e 'string', :boost =\u003e 2.0,            :analyzer =\u003e 'snowball'  },\n            :tags     =\u003e { :type =\u003e 'string', :analyzer =\u003e 'keyword'                             },\n            :content  =\u003e { :type =\u003e 'string', :analyzer =\u003e 'snowball'                            }\n          }\n        }\n      }\n    end\n```\n\nOf course, we may have large amounts of data, and it may be impossible or impractical to add them to the index\none by one. We can use _Elasticsearch's_\n[bulk storage](http://www.elasticsearch.org/guide/reference/api/bulk.html).\nNotice, that collection items must have an `id` property or method,\nand should have a `type` property, if you've set any specific mapping for the index.\n\n```ruby\n    articles = [\n      { :id =\u003e '1', :type =\u003e 'article', :title =\u003e 'one',   :tags =\u003e ['ruby']           },\n      { :id =\u003e '2', :type =\u003e 'article', :title =\u003e 'two',   :tags =\u003e ['ruby', 'python'] },\n      { :id =\u003e '3', :type =\u003e 'article', :title =\u003e 'three', :tags =\u003e ['java']           },\n      { :id =\u003e '4', :type =\u003e 'article', :title =\u003e 'four',  :tags =\u003e ['ruby', 'php']    }\n    ]\n\n    Tire.index 'articles' do\n      import articles\n    end\n```\n\nWe can easily manipulate the documents before storing them in the index, by passing a block to the\n`import` method, like this:\n\n```ruby\n    Tire.index 'articles' do\n      import articles do |documents|\n\n        documents.each { |document| document[:title].capitalize! }\n      end\n\n      refresh\n    end\n```\n\nIf this _declarative_ notation does not fit well in your context,\nyou can use _Tire's_ classes directly, in a more imperative manner:\n\n```ruby\n    index = Tire::Index.new('oldskool')\n    index.delete\n    index.create\n    index.store :title =\u003e \"Let's do it the old way!\"\n    index.refresh\n```\n\nOK. Now, let's go search all the data.\n\nWe will be searching for articles whose `title` begins with letter “T”, sorted by `title` in `descending` order,\nfiltering them for ones tagged “ruby”, and also retrieving some [_facets_](http://www.elasticsearch.org/guide/reference/api/search/facets/)\nfrom the database:\n\n```ruby\n    s = Tire.search 'articles' do\n      query do\n        string 'title:T*'\n      end\n\n      filter :terms, :tags =\u003e ['ruby']\n\n      sort { by :title, 'desc' }\n\n      facet 'global-tags', :global =\u003e true do\n        terms :tags\n      end\n\n      facet 'current-tags' do\n        terms :tags\n      end\n    end\n```\n\n(Of course, we may also page the results with `from` and `size` query options, retrieve only specific fields\nor highlight content matching our query, etc.)\n\nLet's display the results:\n\n```ruby\n    s.results.each do |document|\n      puts \"* #{ document.title } [tags: #{document.tags.join(', ')}]\"\n    end\n\n    # * Two [tags: ruby, python]\n```\n\nLet's display the global facets (distribution of tags across the whole database):\n\n```ruby\n    s.results.facets['global-tags']['terms'].each do |f|\n      puts \"#{f['term'].ljust(10)} #{f['count']}\"\n    end\n\n    # ruby       3\n    # python     1\n    # php        1\n    # java       1\n```\n\nNow, let's display the facets based on current query (notice that count for articles\ntagged with 'java' is included, even though it's not returned by our query;\ncount for articles tagged 'php' is excluded, since they don't match the current query):\n\n```ruby\n    s.results.facets['current-tags']['terms'].each do |f|\n      puts \"#{f['term'].ljust(10)} #{f['count']}\"\n    end\n\n    # ruby       1\n    # python     1\n    # java       1\n```\n\nNotice, that only variables from the enclosing scope are accessible.\nIf we want to access the variables or methods from outer scope,\nwe have to use a slight variation of the DSL, by passing the\n`search` and `query` objects around.\n\n```ruby\n    @query = 'title:T*'\n\n    Tire.search 'articles' do |search|\n      search.query do |query|\n        query.string @query\n      end\n    end\n```\n\nQuite often, we need complex queries with boolean logic.\nInstead of composing long query strings such as `tags:ruby OR tags:java AND NOT tags:python`,\nwe can use the [_bool_](http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html)\nquery. In _Tire_, we build them declaratively.\n\n```ruby\n    Tire.search 'articles' do\n      query do\n        boolean do\n          should   { string 'tags:ruby' }\n          should   { string 'tags:java' }\n          must_not { string 'tags:python' }\n        end\n      end\n    end\n```\n\nThe best thing about `boolean` queries is that we can easily save these partial queries as Ruby blocks,\nto mix and reuse them later. So, we may define a query for the _tags_ property:\n\n```ruby\n    tags_query = lambda do |boolean|\n      boolean.should { string 'tags:ruby' }\n      boolean.should { string 'tags:java' }\n    end\n```\n\nAnd a query for the _published_on_ property:\n\n```ruby\n    published_on_query = lambda do |boolean|\n      boolean.must   { string 'published_on:[2011-01-01 TO 2011-01-02]' }\n    end\n```\n\nNow, we can combine these queries for different searches:\n\n```ruby\n    Tire.search 'articles' do\n      query do\n        boolean \u0026tags_query\n        boolean \u0026published_on_query\n      end\n    end\n```\n\nNote, that you can pass options for configuring queries, facets, etc. by passing a Hash as the last argument to the method call:\n\n```ruby\n    Tire.search 'articles' do\n      query do\n        string 'ruby python', :default_operator =\u003e 'AND', :use_dis_max =\u003e true\n      end\n    end\n```\n\nYou don't have to define the search criteria in one monolithic _Ruby_ block -- you can build the search step by step,\nuntil you call the `results` method:\n\n```ruby\n    s = Tire.search('articles') { query { string 'title:T*' } }\n    s.filter :terms, :tags =\u003e ['ruby']\n    p s.results\n```\n\nIf configuring the search payload with blocks feels somehow too weak for you, you can pass\na plain old Ruby `Hash` (or JSON string) with the query declaration to the `search` method:\n\n```ruby\n    Tire.search 'articles', :query =\u003e { :prefix =\u003e { :title =\u003e 'fou' } }\n```\n\nIf this sounds like a great idea to you, you are probably able to write your application\nusing just `curl`, `sed` and `awk`.\n\nDo note again, however, that you're not tied to the declarative block-style DSL _Tire_ offers to you.\nIf it makes more sense in your context, you can use the API directly, in a more imperative style:\n\n```ruby\n    search = Tire::Search::Search.new('articles')\n    search.query  { string('title:T*') }\n    search.filter :terms, :tags =\u003e ['ruby']\n    search.sort   { by :title, 'desc' }\n    search.facet('global-tags') { terms :tags, :global =\u003e true }\n    # ...\n    p search.results\n```\n\nTo debug the query we have laboriously set up like this,\nwe can display the full query JSON for close inspection:\n\n```ruby\n    puts s.to_json\n    # {\"facets\":{\"current-tags\":{\"terms\":{\"field\":\"tags\"}},\"global-tags\":{\"global\":true,\"terms\":{\"field\":\"tags\"}}},\"query\":{\"query_string\":{\"query\":\"title:T*\"}},\"filter\":{\"terms\":{\"tags\":[\"ruby\"]}},\"sort\":[{\"title\":\"desc\"}]}\n```\n\nOr, better, we can display the corresponding `curl` command to recreate and debug the request in the terminal:\n\n```ruby\n    puts s.to_curl\n    # curl -X POST \"http://localhost:9200/articles/_search?pretty=true\" -d '{\"facets\":{\"current-tags\":{\"terms\":{\"field\":\"tags\"}},\"global-tags\":{\"global\":true,\"terms\":{\"field\":\"tags\"}}},\"query\":{\"query_string\":{\"query\":\"title:T*\"}},\"filter\":{\"terms\":{\"tags\":[\"ruby\"]}},\"sort\":[{\"title\":\"desc\"}]}'\n```\n\nHowever, we can simply log every search query (and other requests) in this `curl`-friendly format:\n\n```ruby\n    Tire.configure { logger 'elasticsearch.log' }\n```\n\nWhen you set the log level to _debug_:\n\n```ruby\n    Tire.configure { logger 'elasticsearch.log', :level =\u003e 'debug' }\n```\n\nthe JSON responses are logged as well. This is not a great idea for production environment,\nbut it's priceless when you want to paste a complicated transaction to the mailing list or IRC channel.\n\nThe _Tire_ DSL tries hard to provide a strong Ruby-like API for the main _Elasticsearch_ features.\n\nBy default, _Tire_ wraps the results collection in a enumerable `Results::Collection` class,\nand result items in a `Results::Item` class, which looks like a child of `Hash` and `Openstruct`,\nfor smooth iterating over and displaying the results.\n\nYou may wrap the result items in your own class by setting the `Tire.configuration.wrapper`\nproperty. Your class must take a `Hash` of attributes on initialization.\n\nIf that seems like a great idea to you, there's a big chance you already have such class.\n\nOne would bet it's an `ActiveRecord` or `ActiveModel` class, containing model of your Rails application.\n\nFortunately, _Tire_ makes blending _Elasticsearch_ features into your models trivially possible.\n\n\nActiveModel Integration\n-----------------------\n\nIf you're the type with no time for lengthy introductions, you can generate a fully working\nexample Rails application, with an `ActiveRecord` model and a search form, to play with\n(it even downloads _Elasticsearch_ itself, generates the application skeleton and leaves you with\na _Git_ repository to explore the steps and the code):\n\n    $ rails new searchapp -m https://raw.github.com/karmi/tire/master/examples/rails-application-template.rb\n\nFor the rest of us, let's suppose you have an `Article` class in your _Rails_ application.\n\nTo make it searchable with _Tire_, just `include` it:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      include Tire::Model::Search\n      include Tire::Model::Callbacks\n    end\n```\n\nWhen you now save a record:\n\n```ruby\n    Article.create :title =\u003e   \"I Love Elasticsearch\",\n                   :content =\u003e \"...\",\n                   :author =\u003e  \"Captain Nemo\",\n                   :published_on =\u003e Time.now\n```\n\nit is automatically added into an index called 'articles', because of the included callbacks.\n\nThe document attributes are indexed exactly as when you call the `Article#to_json` method.\n\nNow you can search the records:\n\n```ruby\n    Article.search 'love'\n```\n\nOK. This is where the search game stops, often. Not here.\n\nFirst of all, you may use the full query DSL, as explained above, with filters, sorting,\nadvanced facet aggregation, highlighting, etc:\n\n```ruby\n    Article.search do\n      query             { string 'love' }\n      facet('timeline') { date   :published_on, :interval =\u003e 'month' }\n      sort              { by     :published_on, 'desc' }\n    end\n```\n\nSecond, dynamic mapping is a godsend when you're prototyping.\nFor serious usage, though, you'll definitely want to define a custom _mapping_ for your models:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      include Tire::Model::Search\n      include Tire::Model::Callbacks\n\n      mapping do\n        indexes :id,           :index    =\u003e :not_analyzed\n        indexes :title,        :analyzer =\u003e 'snowball', :boost =\u003e 100\n        indexes :content,      :analyzer =\u003e 'snowball'\n        indexes :content_size, :as       =\u003e 'content.size'\n        indexes :author,       :analyzer =\u003e 'keyword'\n        indexes :published_on, :type =\u003e 'date', :include_in_all =\u003e false\n      end\n    end\n```\n\nIn this case, _only_ the defined model attributes are indexed. The `mapping` declaration creates the\nindex when the class is loaded or when the importing features are used, and _only_ when it does not yet exist.\n\nYou can define different [_analyzers_](http://www.elasticsearch.org/guide/reference/index-modules/analysis/index.html),\n[_boost_](http://www.elasticsearch.org/guide/reference/mapping/boost-field.html) levels for different properties,\nor any other configuration for _elasticsearch_.\n\nYou're not limited to 1:1 mapping between your model properties and the serialized document. With the `:as` option,\nyou can pass a string or a _Proc_ object which is evaluated in the instance context (see the `content_size` property).\n\nChances are, you want to declare also a custom _settings_ for the index, such as set the number of shards,\nreplicas, or create elaborate analyzer chains, such as the hipster's choice: [_ngrams_](https://gist.github.com/1160430).\nIn this case, just wrap the `mapping` method in a `settings` one, passing it the settings as a Hash:\n\n```ruby\n    class URL \u003c ActiveRecord::Base\n      include Tire::Model::Search\n      include Tire::Model::Callbacks\n\n      settings :number_of_shards =\u003e 1,\n               :number_of_replicas =\u003e 1,\n               :analysis =\u003e {\n                 :filter =\u003e {\n                   :url_ngram  =\u003e {\n                     \"type\"     =\u003e \"nGram\",\n                     \"max_gram\" =\u003e 5,\n                     \"min_gram\" =\u003e 3 }\n                 },\n                 :analyzer =\u003e {\n                   :url_analyzer =\u003e {\n                      \"tokenizer\"    =\u003e \"lowercase\",\n                      \"filter\"       =\u003e [\"stop\", \"url_ngram\"],\n                      \"type\"         =\u003e \"custom\" }\n                 }\n               } do\n        mapping { indexes :url, :type =\u003e 'string', :analyzer =\u003e \"url_analyzer\" }\n      end\n    end\n```\n\nNote, that the index will be created with settings and mappings only when it doesn't exist yet.\nTo re-create the index with correct configuration, delete it first: `URL.index.delete` and\ncreate it afterwards: `URL.create_elasticsearch_index`.\n\nIt may well be reasonable to wrap the index creation logic declared with `Tire.index('urls').create`\nin a class method of your model, in a module method, etc, to have better control on index creation when\nbootstrapping the application with Rake tasks or when setting up the test suite.\n_Tire_ will not hold that against you.\n\nYou may have just stopped wondering: what if I have my own `settings` class method defined?\nOr what if some other gem defines `settings`, or some other _Tire_ method, such as `update_index`?\nThings will break, right? No, they won't.\n\nIn fact, all this time you've been using only _proxies_ to the real _Tire_ methods, which live in the `tire`\nclass and instance methods of your model. Only when not trampling on someone's foot — which is the majority\nof cases —, will _Tire_ bring its methods to the namespace of your class.\n\nSo, instead of writing `Article.search`, you could write `Article.tire.search`, and instead of\n`@article.update_index` you could write `@article.tire.update_index`, to be on the safe side.\nLet's have a look on an example with the `mapping` method:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      include Tire::Model::Search\n      include Tire::Model::Callbacks\n\n      tire.mapping do\n        indexes :id, :type =\u003e 'string', :index =\u003e :not_analyzed\n        # ...\n      end\n    end\n```\n\nOf course, you could also use the block form:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      include Tire::Model::Search\n      include Tire::Model::Callbacks\n\n      tire do\n        mapping do\n          indexes :id, :type =\u003e 'string', :index =\u003e :not_analyzed\n          # ...\n        end\n      end\n    end\n```\n\nInternally, _Tire_ uses these proxy methods exclusively. When you run into issues,\nuse the proxied method, eg. `Article.tire.mapping`, directly.\n\nWhen you want a tight grip on how the attributes are added to the index, just\nimplement the `to_indexed_json` method in your model.\n\nThe easiest way is to customize the `to_json` serialization support of your model:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      # ...\n\n      self.include_root_in_json = false\n      def to_indexed_json\n        to_json :except =\u003e ['updated_at'], :methods =\u003e ['length']\n      end\n    end\n```\n\nOf course, it may well be reasonable to define the indexed JSON from the ground up:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      # ...\n\n      def to_indexed_json\n        names      = author.split(/\\W/)\n        last_name  = names.pop\n        first_name = names.join\n\n        {\n          :title   =\u003e title,\n          :content =\u003e content,\n          :author  =\u003e {\n            :first_name =\u003e first_name,\n            :last_name  =\u003e last_name\n          }\n        }.to_json\n      end\n    end\n```\n\nNotice, that you may want to skip including the `Tire::Model::Callbacks` module in special cases,\nlike when your records are indexed via some external mechanism, let's say a _CouchDB_ or _RabbitMQ_\n[river](http://www.elasticsearch.org/blog/2010/09/28/the_river.html), or when you need better\ncontrol on how the documents are added to or removed from the index:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      include Tire::Model::Search\n\n      after_save do\n        update_index if state == 'published'\n      end\n    end\n```\n\nSometimes, you might want to have complete control about the indexing process. In such situations,\njust drop down one layer and use the `Tire::Index#store` and `Tire::Index#remove` methods directly:\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      acts_as_paranoid\n      include Tire::Model::Search\n\n      after_save do\n        if deleted_at.nil?\n          self.index.store self\n        else\n          self.index.remove self\n        end\n      end\n    end\n```\n\nOf course, in this way, you're still performing an HTTP request during your database transaction,\nwhich is not optimal for large-scale applications. In these situations, a better option would be processing\nthe index operations in background, with something like [Resque](https://github.com/resque/resque) or\n[Sidekiq](https://github.com/mperham/sidekiq):\n\n```ruby\n    class Article \u003c ActiveRecord::Base\n      include Tire::Model::Search\n\n      after_save    { Indexer::Index.perform_async(document) }\n      after_destroy { Indexer::Remove.perform_async(document) }\n    end\n```\n\nWhen you're integrating _Tire_ with ActiveRecord models, you should use the `after_commit`\nand `after_rollback` hooks to keep the index in sync with your database.\n\nThe results returned by `Article.search` are wrapped in the aforementioned `Item` class, by default.\nThis way, we have a fast and flexible access to the properties returned from _Elasticsearch_ (via the\n`_source` or `fields` JSON properties). This way, we can index whatever JSON we like in _Elasticsearch_,\nand retrieve it, simply, via the dot notation:\n\n```ruby\n    articles = Article.search 'love'\n    articles.each do |article|\n      puts article.title\n      puts article.author.last_name\n    end\n```\n\nThe `Item` instances masquerade themselves as instances of your model within a _Rails_ application\n(based on the `_type` property retrieved from _Elasticsearch_), so you can use them carefree;\nall the `url_for` or `dom_id` helpers work as expected.\n\nIf you need to access the “real” model (eg. to access its associations or methods not\nstored in _Elasticsearch_), just load it from the database:\n\n```ruby\n    puts article.load(:include =\u003e 'comments').comments.size\n```\n\nYou can see that _Tire_ stays as far from the database as possible. That's because it believes\nyou have most of the data you want to display stored in _Elasticsearch_. When you need\nto eagerly load the records from the database itself, for whatever reason,\nyou can do it with the `:load` option when searching:\n\n```ruby\n    # Will call `Article.search [1, 2, 3]`\n    Article.search 'love', :load =\u003e true\n```\n\nInstead of simple `true`, you can pass any options for the model's find method:\n\n```ruby\n    # Will call `Article.search [1, 2, 3], :include =\u003e 'comments'`\n    Article.search :load =\u003e { :include =\u003e 'comments' } do\n      query { string 'love' }\n    end\n```\n\nIf you would like to access properties returned by Elasticsearch (such as `_score`),\nin addition to model instance, use the `each_with_hit` method:\n\n```ruby\n    results = Article.search 'One', :load =\u003e true\n    results.each_with_hit do |result, hit|\n      puts \"#{result.title} (score: #{hit['_score']})\"\n    end\n\n    # One (score: 0.300123)\n```\n\nNote that _Tire_ search results are fully compatible with [_WillPaginate_](https://github.com/mislav/will_paginate)\nand [_Kaminari_](https://github.com/amatsuda/kaminari), so you can pass all the usual parameters to the\n`search` method in the controller:\n\n```ruby\n    @articles = Article.search params[:q], :page =\u003e (params[:page] || 1)\n```\n\nOK. Chances are, you have lots of records stored in your database. How will you get them to _Elasticsearch_? Easy:\n\n```ruby\n    Article.index.import Article.all\n```\n\nThis way, however, all your records are loaded into memory, serialized into JSON,\nand sent down the wire to _Elasticsearch_. Not practical, you say? You're right.\n\nWhen your model is an `ActiveRecord::Base` or `Mongoid::Document` one, or when it implements\nsome sort of pagination, you can just run:\n\n```ruby\n    Article.import\n```\n\nDepending on the setup of your model, either `find_in_batches`, `limit..skip` or pagination is used\nto import your data.\n\nAre we saying you have to fiddle with this thing in a `rails console` or silly Ruby scripts? No.\nJust call the included _Rake_ task on the command line:\n\n```bash\n    $ rake environment tire:import:all\n```\n\nYou can also force-import the data by deleting the index first (and creating it with\ncorrect settings and/or mappings provided by the `mapping` block in your model):\n\n```bash\n    $ rake environment tire:import CLASS='Article' FORCE=true\n```\n\nWhen you'll spend more time with _Elasticsearch_, you'll notice how\n[index aliases](http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html)\nare the best idea since the invention of inverted index.\nYou can index your data into a fresh index (and possibly update an alias once everything's fine):\n\n```bash\n    $ rake environment tire:import CLASS='Article' INDEX='articles-2011-05'\n```\n\nFinally, consider the Rake importing task just a convenient starting point. If you're loading\nsubstantial amounts of data, want better control on which data will be indexed, etc., use the\nlower-level Tire API with eg. `ActiveRecordBase#find_in_batches` directly:\n\n```ruby\n    Article.where(\"published_on \u003e ?\", Time.parse(\"2012-10-01\")).find_in_batches(include: authors) do |batch|\n      Tire.index(\"articles\").import batch\n    end\n```\nIf you're using a different database, such as [MongoDB](http://www.mongodb.org/),\nanother object mapping library, such as [Mongoid](http://mongoid.org/) or [MongoMapper](http://mongomapper.com/),\nthings stay mostly the same:\n\n```ruby\n    class Article\n      include Mongoid::Document\n      field :title, :type =\u003e String\n      field :content, :type =\u003e String\n\n      include Tire::Model::Search\n      include Tire::Model::Callbacks\n\n      # These Mongo guys sure do get funky with their IDs in +serializable_hash+, let's fix it.\n      #\n      def to_indexed_json\n        self.to_json\n      end\n\n    end\n\n    Article.create :title =\u003e 'I Love Elasticsearch'\n\n    Article.tire.search 'love'\n```\n\n_Tire_ does not care what's your primary data storage solution, if it has an _ActiveModel_-compatible\nadapter. But there's more.\n\n_Tire_ implements not only _searchable_ features, but also _persistence_ features. This means you can use a _Tire_ model **instead of your database**, not just for _searching_ your database. Why would you like to do that?\n\nWell, because you're tired of database migrations and lots of hand-holding with your\ndatabase to store stuff like `{ :name =\u003e 'Tire', :tags =\u003e [ 'ruby', 'search' ] }`.\nBecause all you need, really, is to just dump a JSON-representation of your data into a database and load it back again.\nBecause you've noticed that _searching_ your data is a much more effective way of retrieval\nthen constructing elaborate database query conditions.\nBecause you have _lots_ of data and want to use _Elasticsearch's_ advanced distributed features.\n\nAll good reasons to use _Elasticsearch_ as a schema-free and highly-scalable storage and retrieval/aggregation engine for your data.\n\nTo use the persistence mode, we'll include the `Tire::Persistence` module in our class and define its properties;\nwe can add the standard mapping declarations, set default values, or define casting for the property to create\nlightweight associations between the models.\n\n```ruby\n    class Article\n      include Tire::Model::Persistence\n\n      validates_presence_of :title, :author\n\n      property :title,        :analyzer =\u003e 'snowball'\n      property :published_on, :type =\u003e 'date'\n      property :tags,         :default =\u003e [], :analyzer =\u003e 'keyword'\n      property :author,       :class =\u003e Author\n      property :comments,     :class =\u003e [Comment]\n    end\n```\n\nPlease be sure to peruse the [integration test suite](https://github.com/karmi/tire/tree/master/test/integration)\nfor examples of the API and _ActiveModel_ integration usage.\n\n\nExtensions and Additions\n------------------------\n\nThe [_tire-contrib_](http://github.com/karmi/tire-contrib/) project contains additions\nand extensions to the core _Tire_ functionality — be sure to check them out.\n\n\nOther Clients\n-------------\n\nCheck out [other _Elasticsearch_ clients](http://www.elasticsearch.org/guide/clients/).\n\n\nFeedback\n--------\n\nYou can send feedback via [e-mail](mailto:karmi@karmi.cz) or via [Github Issues](https://github.com/karmi/tire/issues).\n\n-----\n\n[Karel Minarik](http://karmi.cz) and [contributors](http://github.com/karmi/tire/contributors)\n\n![](https://ga-beacon.appspot.com/UA-46901128-1/karmi/retire?pixel)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarmi%2Fretire","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarmi%2Fretire","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarmi%2Fretire/lists"}