elasticsearch-persistence 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
 - data/CHANGELOG.md +4 -0
 - data/README.md +238 -7
 - data/elasticsearch-persistence.gemspec +4 -1
 - data/examples/music/album.rb +34 -0
 - data/examples/music/artist.rb +50 -0
 - data/examples/music/artists/_form.html.erb +8 -0
 - data/examples/music/artists/artists_controller.rb +67 -0
 - data/examples/music/artists/artists_controller_test.rb +53 -0
 - data/examples/music/artists/index.html.erb +57 -0
 - data/examples/music/artists/show.html.erb +51 -0
 - data/examples/music/assets/application.css +226 -0
 - data/examples/music/assets/autocomplete.css +48 -0
 - data/examples/music/assets/blank_cover.png +0 -0
 - data/examples/music/assets/form.css +113 -0
 - data/examples/music/index_manager.rb +60 -0
 - data/examples/music/search/index.html.erb +93 -0
 - data/examples/music/search/search_controller.rb +41 -0
 - data/examples/music/search/search_controller_test.rb +9 -0
 - data/examples/music/search/search_helper.rb +15 -0
 - data/examples/music/suggester.rb +45 -0
 - data/examples/music/template.rb +392 -0
 - data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.css +7 -0
 - data/examples/music/vendor/assets/jquery-ui-1.10.4.custom.min.js +6 -0
 - data/examples/{sinatra → notes}/.gitignore +0 -0
 - data/examples/{sinatra → notes}/Gemfile +0 -0
 - data/examples/{sinatra → notes}/README.markdown +0 -0
 - data/examples/{sinatra → notes}/application.rb +0 -0
 - data/examples/{sinatra → notes}/config.ru +0 -0
 - data/examples/{sinatra → notes}/test.rb +0 -0
 - data/lib/elasticsearch/persistence.rb +19 -0
 - data/lib/elasticsearch/persistence/model.rb +129 -0
 - data/lib/elasticsearch/persistence/model/base.rb +75 -0
 - data/lib/elasticsearch/persistence/model/errors.rb +8 -0
 - data/lib/elasticsearch/persistence/model/find.rb +171 -0
 - data/lib/elasticsearch/persistence/model/rails.rb +39 -0
 - data/lib/elasticsearch/persistence/model/store.rb +239 -0
 - data/lib/elasticsearch/persistence/model/utils.rb +0 -0
 - data/lib/elasticsearch/persistence/repository.rb +3 -1
 - data/lib/elasticsearch/persistence/repository/search.rb +25 -0
 - data/lib/elasticsearch/persistence/version.rb +1 -1
 - data/lib/rails/generators/elasticsearch/model/model_generator.rb +21 -0
 - data/lib/rails/generators/elasticsearch/model/templates/model.rb.tt +9 -0
 - data/lib/rails/generators/elasticsearch_generator.rb +2 -0
 - data/test/integration/model/model_basic_test.rb +157 -0
 - data/test/integration/repository/default_class_test.rb +6 -0
 - data/test/unit/model_base_test.rb +40 -0
 - data/test/unit/model_find_test.rb +147 -0
 - data/test/unit/model_gateway_test.rb +99 -0
 - data/test/unit/model_rails_test.rb +88 -0
 - data/test/unit/model_store_test.rb +493 -0
 - data/test/unit/repository_search_test.rb +17 -0
 - metadata +79 -9
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,15 +1,15 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            !binary "U0hBMQ==":
         
     | 
| 
       3 
3 
     | 
    
         
             
              metadata.gz: !binary |-
         
     | 
| 
       4 
     | 
    
         
            -
                 
     | 
| 
      
 4 
     | 
    
         
            +
                OTQ2NWJlZWJlNDE0ODRhODAzZDM2ZDFhYTFiNDkzNTdlMTZkZjQzYQ==
         
     | 
| 
       5 
5 
     | 
    
         
             
              data.tar.gz: !binary |-
         
     | 
| 
       6 
     | 
    
         
            -
                 
     | 
| 
      
 6 
     | 
    
         
            +
                MmUzOGJiN2I0MmY1ZDBmOTg3NjRiOWM0NDk2NjkzN2Q0N2I2Yjk3Mg==
         
     | 
| 
       7 
7 
     | 
    
         
             
            SHA512:
         
     | 
| 
       8 
8 
     | 
    
         
             
              metadata.gz: !binary |-
         
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
                 
     | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
      
 9 
     | 
    
         
            +
                YTViMmMzZTNlMGVhMTE4ZWIzYzJmYjU1ZGQwMWJlMzVmMWQ4ZmM2Y2YyNTcz
         
     | 
| 
      
 10 
     | 
    
         
            +
                NWQ2NDE2NDlkYzI2YTFkMjMxOWVlMWZlZWE5MWQ4MmNlNjQxMzc1MWVkNzZk
         
     | 
| 
      
 11 
     | 
    
         
            +
                MTcxYjM2OTk1MDNlNDQ5NjNiNzk1NTkxNGVlNjVjNTMyZTI4Mzk=
         
     | 
| 
       12 
12 
     | 
    
         
             
              data.tar.gz: !binary |-
         
     | 
| 
       13 
     | 
    
         
            -
                 
     | 
| 
       14 
     | 
    
         
            -
                 
     | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
      
 13 
     | 
    
         
            +
                ZjFmNDc0MWYyOTZjNTcwZmNjMzBmYzY5ZTlmZmFmODM3MzEwNTg0OTAwYmY5
         
     | 
| 
      
 14 
     | 
    
         
            +
                MzZkZTFmZjNmNzliZTFiNTY4YTZhMjVhYWY1MmNmZWRiNTJmZWYwNDNiYWFj
         
     | 
| 
      
 15 
     | 
    
         
            +
                ZmRhMmZhNzg1ZTAyNDI3ODJhODRlMGI3MjI5NDczZTNhYmU4NzQ=
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -416,17 +416,248 @@ results.response._shards.failed 
     | 
|
| 
       416 
416 
     | 
    
         | 
| 
       417 
417 
     | 
    
         
             
            #### Example Application
         
     | 
| 
       418 
418 
     | 
    
         | 
| 
       419 
     | 
    
         
            -
            An example Sinatra application is available in
         
     | 
| 
       420 
     | 
    
         
            -
             
     | 
| 
       421 
     | 
    
         
            -
            and demonstrates a rich set of features of the repository.
         
     | 
| 
      
 419 
     | 
    
         
            +
            An example Sinatra application is available in [`examples/notes/application.rb`](examples/notes/application.rb),
         
     | 
| 
      
 420 
     | 
    
         
            +
            and demonstrates a rich set of features:
         
     | 
| 
       422 
421 
     | 
    
         | 
| 
      
 422 
     | 
    
         
            +
            * How to create and configure a custom repository class
         
     | 
| 
      
 423 
     | 
    
         
            +
            * How to work with a plain Ruby class as the domain object
         
     | 
| 
      
 424 
     | 
    
         
            +
            * How to integrate the repository with a Sinatra application
         
     | 
| 
      
 425 
     | 
    
         
            +
            * How to write complex search definitions, including pagination, highlighting and aggregations
         
     | 
| 
      
 426 
     | 
    
         
            +
            * How to use search results in the application view
         
     | 
| 
       423 
427 
     | 
    
         | 
| 
       424 
428 
     | 
    
         
             
            ### The ActiveRecord Pattern
         
     | 
| 
       425 
429 
     | 
    
         | 
| 
       426 
     | 
    
         
            -
             
     | 
| 
       427 
     | 
    
         
            -
             
     | 
| 
       428 
     | 
    
         
            -
             
     | 
| 
       429 
     | 
    
         
            -
             
     | 
| 
      
 430 
     | 
    
         
            +
            The `Elasticsearch::Persistence::Model` module provides an implementation of the
         
     | 
| 
      
 431 
     | 
    
         
            +
            active record [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html),
         
     | 
| 
      
 432 
     | 
    
         
            +
            with a familiar interface for using Elasticsearch as a persistence layer in
         
     | 
| 
      
 433 
     | 
    
         
            +
            Ruby on Rails applications.
         
     | 
| 
      
 434 
     | 
    
         
            +
             
     | 
| 
      
 435 
     | 
    
         
            +
            All the methods are documented with comprehensive examples in the source code,
         
     | 
| 
      
 436 
     | 
    
         
            +
            available also online at <http://rubydoc.info/gems/elasticsearch-persistence/Elasticsearch/Persistence/Model>.
         
     | 
| 
      
 437 
     | 
    
         
            +
             
     | 
| 
      
 438 
     | 
    
         
            +
            #### Installation/Usage
         
     | 
| 
      
 439 
     | 
    
         
            +
             
     | 
| 
      
 440 
     | 
    
         
            +
            To use the library in a Rails application, add it to your `Gemfile` with a `require` statement:
         
     | 
| 
      
 441 
     | 
    
         
            +
             
     | 
| 
      
 442 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 443 
     | 
    
         
            +
            gem "elasticsearch-persistence", require: 'elasticsearch/persistence/model'
         
     | 
| 
      
 444 
     | 
    
         
            +
            ```
         
     | 
| 
      
 445 
     | 
    
         
            +
             
     | 
| 
      
 446 
     | 
    
         
            +
            To use the library without Bundler, install it, and require the file:
         
     | 
| 
      
 447 
     | 
    
         
            +
             
     | 
| 
      
 448 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 449 
     | 
    
         
            +
            gem install elasticsearch-persistence
         
     | 
| 
      
 450 
     | 
    
         
            +
            ```
         
     | 
| 
      
 451 
     | 
    
         
            +
             
     | 
| 
      
 452 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 453 
     | 
    
         
            +
            # In your code
         
     | 
| 
      
 454 
     | 
    
         
            +
            require 'elasticsearch/persistence/model'
         
     | 
| 
      
 455 
     | 
    
         
            +
            ```
         
     | 
| 
      
 456 
     | 
    
         
            +
             
     | 
| 
      
 457 
     | 
    
         
            +
            #### Model Definition
         
     | 
| 
      
 458 
     | 
    
         
            +
             
     | 
| 
      
 459 
     | 
    
         
            +
            The integration is implemented by including the module in a Ruby class.
         
     | 
| 
      
 460 
     | 
    
         
            +
            The model attribute definition support is implemented with the
         
     | 
| 
      
 461 
     | 
    
         
            +
            [_Virtus_](https://github.com/solnic/virtus) Rubygem, and the
         
     | 
| 
      
 462 
     | 
    
         
            +
            naming, validation, etc. features with the
         
     | 
| 
      
 463 
     | 
    
         
            +
            [_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem.
         
     | 
| 
      
 464 
     | 
    
         
            +
             
     | 
| 
      
 465 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 466 
     | 
    
         
            +
            class Article
         
     | 
| 
      
 467 
     | 
    
         
            +
              include Elasticsearch::Persistence::Model
         
     | 
| 
      
 468 
     | 
    
         
            +
             
     | 
| 
      
 469 
     | 
    
         
            +
              # Define a plain `title` attribute
         
     | 
| 
      
 470 
     | 
    
         
            +
              #
         
     | 
| 
      
 471 
     | 
    
         
            +
              attribute :title,  String
         
     | 
| 
      
 472 
     | 
    
         
            +
             
     | 
| 
      
 473 
     | 
    
         
            +
              # Define an `author` attribute, with multiple analyzers for this field
         
     | 
| 
      
 474 
     | 
    
         
            +
              #
         
     | 
| 
      
 475 
     | 
    
         
            +
              attribute :author, String, mapping: { fields: {
         
     | 
| 
      
 476 
     | 
    
         
            +
                                           author: { type: 'string'},
         
     | 
| 
      
 477 
     | 
    
         
            +
                                           raw:    { type: 'string', analyzer: 'keyword' }
         
     | 
| 
      
 478 
     | 
    
         
            +
                                         } }
         
     | 
| 
      
 479 
     | 
    
         
            +
             
     | 
| 
      
 480 
     | 
    
         
            +
             
     | 
| 
      
 481 
     | 
    
         
            +
              # Define a `views` attribute, with default value
         
     | 
| 
      
 482 
     | 
    
         
            +
              #
         
     | 
| 
      
 483 
     | 
    
         
            +
              attribute :views,  Integer, default: 0, mapping: { type: 'integer' }
         
     | 
| 
      
 484 
     | 
    
         
            +
             
     | 
| 
      
 485 
     | 
    
         
            +
              # Validate the presence of the `title` attribute
         
     | 
| 
      
 486 
     | 
    
         
            +
              #
         
     | 
| 
      
 487 
     | 
    
         
            +
              validates :title, presence: true
         
     | 
| 
      
 488 
     | 
    
         
            +
             
     | 
| 
      
 489 
     | 
    
         
            +
              # Execute code after saving the model.
         
     | 
| 
      
 490 
     | 
    
         
            +
              #
         
     | 
| 
      
 491 
     | 
    
         
            +
              after_save { puts "Successfuly saved: #{self}" }
         
     | 
| 
      
 492 
     | 
    
         
            +
            end
         
     | 
| 
      
 493 
     | 
    
         
            +
            ```
         
     | 
| 
      
 494 
     | 
    
         
            +
             
     | 
| 
      
 495 
     | 
    
         
            +
            Attribute validations works like for any other _ActiveModel_-compatible implementation:
         
     | 
| 
      
 496 
     | 
    
         
            +
             
     | 
| 
      
 497 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 498 
     | 
    
         
            +
            article = Article.new                                                                                             # => #<Article { ... }>
         
     | 
| 
      
 499 
     | 
    
         
            +
             
     | 
| 
      
 500 
     | 
    
         
            +
            article.valid?
         
     | 
| 
      
 501 
     | 
    
         
            +
            # => false
         
     | 
| 
      
 502 
     | 
    
         
            +
             
     | 
| 
      
 503 
     | 
    
         
            +
            article.errors.to_a
         
     | 
| 
      
 504 
     | 
    
         
            +
            # => ["Title can't be blank"]
         
     | 
| 
      
 505 
     | 
    
         
            +
            ```
         
     | 
| 
      
 506 
     | 
    
         
            +
             
     | 
| 
      
 507 
     | 
    
         
            +
            #### Persistence
         
     | 
| 
      
 508 
     | 
    
         
            +
             
     | 
| 
      
 509 
     | 
    
         
            +
            We can create a new article in the database...
         
     | 
| 
      
 510 
     | 
    
         
            +
             
     | 
| 
      
 511 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 512 
     | 
    
         
            +
            Article.create id: 1, title: 'Test', author: 'John'
         
     | 
| 
      
 513 
     | 
    
         
            +
            # PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a]
         
     | 
| 
      
 514 
     | 
    
         
            +
            ```
         
     | 
| 
      
 515 
     | 
    
         
            +
             
     | 
| 
      
 516 
     | 
    
         
            +
            ... and find it:
         
     | 
| 
      
 517 
     | 
    
         
            +
             
     | 
| 
      
 518 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 519 
     | 
    
         
            +
            article = Article.find(1)
         
     | 
| 
      
 520 
     | 
    
         
            +
            # => #<Article { ... }>
         
     | 
| 
      
 521 
     | 
    
         
            +
             
     | 
| 
      
 522 
     | 
    
         
            +
            article._index
         
     | 
| 
      
 523 
     | 
    
         
            +
            # => "articles"
         
     | 
| 
      
 524 
     | 
    
         
            +
             
     | 
| 
      
 525 
     | 
    
         
            +
            article.id
         
     | 
| 
      
 526 
     | 
    
         
            +
            # => "1"
         
     | 
| 
      
 527 
     | 
    
         
            +
             
     | 
| 
      
 528 
     | 
    
         
            +
            article.title
         
     | 
| 
      
 529 
     | 
    
         
            +
            # => "Test"
         
     | 
| 
      
 530 
     | 
    
         
            +
            ```
         
     | 
| 
      
 531 
     | 
    
         
            +
             
     | 
| 
      
 532 
     | 
    
         
            +
            To update the model, either update the attribute and save the model:
         
     | 
| 
      
 533 
     | 
    
         
            +
             
     | 
| 
      
 534 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 535 
     | 
    
         
            +
            article.title = 'Updated'
         
     | 
| 
      
 536 
     | 
    
         
            +
             
     | 
| 
      
 537 
     | 
    
         
            +
            article.save
         
     | 
| 
      
 538 
     | 
    
         
            +
            => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false}
         
     | 
| 
      
 539 
     | 
    
         
            +
            ```
         
     | 
| 
      
 540 
     | 
    
         
            +
             
     | 
| 
      
 541 
     | 
    
         
            +
            ... or use the `update_attributes` method:
         
     | 
| 
      
 542 
     | 
    
         
            +
             
     | 
| 
      
 543 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 544 
     | 
    
         
            +
            article.update_attributes title: 'Test', author: 'Mary'
         
     | 
| 
      
 545 
     | 
    
         
            +
            # => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3}
         
     | 
| 
      
 546 
     | 
    
         
            +
            ```
         
     | 
| 
      
 547 
     | 
    
         
            +
             
     | 
| 
      
 548 
     | 
    
         
            +
            The implementation supports the familiar interface for updating model timestamps:
         
     | 
| 
      
 549 
     | 
    
         
            +
             
     | 
| 
      
 550 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 551 
     | 
    
         
            +
            article.touch
         
     | 
| 
      
 552 
     | 
    
         
            +
            # => => { ... "_version"=>4}
         
     | 
| 
      
 553 
     | 
    
         
            +
            ```
         
     | 
| 
      
 554 
     | 
    
         
            +
             
     | 
| 
      
 555 
     | 
    
         
            +
            ... and numeric attributes:
         
     | 
| 
      
 556 
     | 
    
         
            +
             
     | 
| 
      
 557 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 558 
     | 
    
         
            +
            article.views
         
     | 
| 
      
 559 
     | 
    
         
            +
            # => 0
         
     | 
| 
      
 560 
     | 
    
         
            +
             
     | 
| 
      
 561 
     | 
    
         
            +
            article.increment :views
         
     | 
| 
      
 562 
     | 
    
         
            +
            article.views
         
     | 
| 
      
 563 
     | 
    
         
            +
            # => 1
         
     | 
| 
      
 564 
     | 
    
         
            +
            ```
         
     | 
| 
      
 565 
     | 
    
         
            +
             
     | 
| 
      
 566 
     | 
    
         
            +
            Any callbacks defined in the model will be triggered during the persistence operations:
         
     | 
| 
      
 567 
     | 
    
         
            +
             
     | 
| 
      
 568 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 569 
     | 
    
         
            +
            article.save
         
     | 
| 
      
 570 
     | 
    
         
            +
            # Successfuly saved: #<Article {...}>
         
     | 
| 
      
 571 
     | 
    
         
            +
            ```
         
     | 
| 
      
 572 
     | 
    
         
            +
             
     | 
| 
      
 573 
     | 
    
         
            +
            The model also supports familiar `find_in_batches` and `find_each` methods to efficiently
         
     | 
| 
      
 574 
     | 
    
         
            +
            retrieve big collections of model instance, using the Elasticsearch's _Scan API_:
         
     | 
| 
      
 575 
     | 
    
         
            +
             
     | 
| 
      
 576 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 577 
     | 
    
         
            +
            Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" }
         
     | 
| 
      
 578 
     | 
    
         
            +
            # GET http://localhost:9200/articles/article/_search?scroll=5m&search_type=scan&size=20
         
     | 
| 
      
 579 
     | 
    
         
            +
            # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
         
     | 
| 
      
 580 
     | 
    
         
            +
            # ===> TEST
         
     | 
| 
      
 581 
     | 
    
         
            +
            # GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
         
     | 
| 
      
 582 
     | 
    
         
            +
            # => "c2Nhb..."
         
     | 
| 
      
 583 
     | 
    
         
            +
            ```
         
     | 
| 
      
 584 
     | 
    
         
            +
             
     | 
| 
      
 585 
     | 
    
         
            +
            #### Search
         
     | 
| 
      
 586 
     | 
    
         
            +
             
     | 
| 
      
 587 
     | 
    
         
            +
            The model class provides a `search` method to retrieve model instances with a regular
         
     | 
| 
      
 588 
     | 
    
         
            +
            search definition, including highlighting, aggregations, etc:
         
     | 
| 
      
 589 
     | 
    
         
            +
             
     | 
| 
      
 590 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 591 
     | 
    
         
            +
            results = Article.search query: { match: { title: 'test' } },
         
     | 
| 
      
 592 
     | 
    
         
            +
                                     aggregations: {  authors: { terms: { field: 'author.raw' } } },
         
     | 
| 
      
 593 
     | 
    
         
            +
                                     highlight: { fields: { title: {} } }
         
     | 
| 
      
 594 
     | 
    
         
            +
             
     | 
| 
      
 595 
     | 
    
         
            +
            puts results.first.title
         
     | 
| 
      
 596 
     | 
    
         
            +
            # Test
         
     | 
| 
      
 597 
     | 
    
         
            +
             
     | 
| 
      
 598 
     | 
    
         
            +
            puts results.first.hit.highlight['title']
         
     | 
| 
      
 599 
     | 
    
         
            +
            # <em>Test</em>
         
     | 
| 
      
 600 
     | 
    
         
            +
             
     | 
| 
      
 601 
     | 
    
         
            +
            puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} : #{b['doc_count']}" }
         
     | 
| 
      
 602 
     | 
    
         
            +
            # John : 1
         
     | 
| 
      
 603 
     | 
    
         
            +
            ```
         
     | 
| 
      
 604 
     | 
    
         
            +
             
     | 
| 
      
 605 
     | 
    
         
            +
            #### Accessing the Repository Gateway
         
     | 
| 
      
 606 
     | 
    
         
            +
             
     | 
| 
      
 607 
     | 
    
         
            +
            The Elasticsearch integration is implemented by embedding the repository object in the model.
         
     | 
| 
      
 608 
     | 
    
         
            +
            You can access it through the `gateway` method:
         
     | 
| 
      
 609 
     | 
    
         
            +
             
     | 
| 
      
 610 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 611 
     | 
    
         
            +
            Artist.gateway.client.info
         
     | 
| 
      
 612 
     | 
    
         
            +
            # GET http://localhost:9200/ [status:200, request:0.011s, query:n/a]
         
     | 
| 
      
 613 
     | 
    
         
            +
            # => {"status"=>200, "name"=>"Lightspeed", ...}
         
     | 
| 
      
 614 
     | 
    
         
            +
            ```
         
     | 
| 
      
 615 
     | 
    
         
            +
             
     | 
| 
      
 616 
     | 
    
         
            +
            #### Rails Compatibility
         
     | 
| 
      
 617 
     | 
    
         
            +
             
     | 
| 
      
 618 
     | 
    
         
            +
            The model instances are fully compatible with Rails' conventions and helpers:
         
     | 
| 
      
 619 
     | 
    
         
            +
             
     | 
| 
      
 620 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 621 
     | 
    
         
            +
            url_for article
         
     | 
| 
      
 622 
     | 
    
         
            +
            # => "http://localhost:3000/articles/1"
         
     | 
| 
      
 623 
     | 
    
         
            +
             
     | 
| 
      
 624 
     | 
    
         
            +
            div_for article
         
     | 
| 
      
 625 
     | 
    
         
            +
            # => '<div class="article" id="article_1"></div>'
         
     | 
| 
      
 626 
     | 
    
         
            +
            ```
         
     | 
| 
      
 627 
     | 
    
         
            +
             
     | 
| 
      
 628 
     | 
    
         
            +
            ... as well as form values for dates and times:
         
     | 
| 
      
 629 
     | 
    
         
            +
             
     | 
| 
      
 630 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 631 
     | 
    
         
            +
            article = Article.new "title" => "Date", "published(1i)"=>"2014", "published(2i)"=>"1", "published(3i)"=>"1"
         
     | 
| 
      
 632 
     | 
    
         
            +
             
     | 
| 
      
 633 
     | 
    
         
            +
            article.published.iso8601
         
     | 
| 
      
 634 
     | 
    
         
            +
            # => "2014-01-01"
         
     | 
| 
      
 635 
     | 
    
         
            +
            ```
         
     | 
| 
      
 636 
     | 
    
         
            +
             
     | 
| 
      
 637 
     | 
    
         
            +
            The library provides a Rails ORM generator:
         
     | 
| 
      
 638 
     | 
    
         
            +
             
     | 
| 
      
 639 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 640 
     | 
    
         
            +
            rails generate scaffold Person name:String email:String birthday:Date --orm=elasticsearch
         
     | 
| 
      
 641 
     | 
    
         
            +
            ```
         
     | 
| 
      
 642 
     | 
    
         
            +
             
     | 
| 
      
 643 
     | 
    
         
            +
            #### Example application
         
     | 
| 
      
 644 
     | 
    
         
            +
             
     | 
| 
      
 645 
     | 
    
         
            +
            A fully working Ruby on Rails application can be generated with the following command:
         
     | 
| 
      
 646 
     | 
    
         
            +
             
     | 
| 
      
 647 
     | 
    
         
            +
            ```bash
         
     | 
| 
      
 648 
     | 
    
         
            +
            rails new music --force --skip --skip-bundle --skip-active-record --template https://raw.githubusercontent.com/elasticsearch/elasticsearch-rails/persistence-model/elasticsearch-persistence/examples/music/template.rb
         
     | 
| 
      
 649 
     | 
    
         
            +
            ```
         
     | 
| 
      
 650 
     | 
    
         
            +
             
     | 
| 
      
 651 
     | 
    
         
            +
            The application demonstrates:
         
     | 
| 
      
 652 
     | 
    
         
            +
             
     | 
| 
      
 653 
     | 
    
         
            +
            * How to set up model attributes with custom mappings
         
     | 
| 
      
 654 
     | 
    
         
            +
            * How to configure model relationships with Elasticsearch's parent/child
         
     | 
| 
      
 655 
     | 
    
         
            +
            * How to configure models to use a common index, and create the index with proper mappings
         
     | 
| 
      
 656 
     | 
    
         
            +
            * How to use Elasticsearch's completion suggester to drive auto-complete functionality
         
     | 
| 
      
 657 
     | 
    
         
            +
            * How to use Elasticsearch-persisted model in Rails' views and forms
         
     | 
| 
      
 658 
     | 
    
         
            +
            * How to write controller tests
         
     | 
| 
      
 659 
     | 
    
         
            +
             
     | 
| 
      
 660 
     | 
    
         
            +
            The source files for the application are available in the [`examples/music`](examples/music) folder.
         
     | 
| 
       430 
661 
     | 
    
         | 
| 
       431 
662 
     | 
    
         
             
            ## License
         
     | 
| 
       432 
663 
     | 
    
         | 
| 
         @@ -26,13 +26,16 @@ Gem::Specification.new do |s| 
     | 
|
| 
       26 
26 
     | 
    
         
             
              s.add_dependency "elasticsearch",       '> 0.4'
         
     | 
| 
       27 
27 
     | 
    
         
             
              s.add_dependency "elasticsearch-model", '>= 0.1'
         
     | 
| 
       28 
28 
     | 
    
         
             
              s.add_dependency "activesupport",       '> 3'
         
     | 
| 
      
 29 
     | 
    
         
            +
              s.add_dependency "activemodel",         '> 3'
         
     | 
| 
       29 
30 
     | 
    
         
             
              s.add_dependency "hashie"
         
     | 
| 
      
 31 
     | 
    
         
            +
              s.add_dependency "virtus"
         
     | 
| 
       30 
32 
     | 
    
         | 
| 
       31 
33 
     | 
    
         
             
              s.add_development_dependency "bundler", "~> 1.5"
         
     | 
| 
       32 
34 
     | 
    
         
             
              s.add_development_dependency "rake"
         
     | 
| 
       33 
35 
     | 
    
         | 
| 
       34 
36 
     | 
    
         
             
              s.add_development_dependency "oj"
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              s.add_development_dependency "rails"
         
     | 
| 
       36 
39 
     | 
    
         | 
| 
       37 
40 
     | 
    
         
             
              s.add_development_dependency "elasticsearch-extensions"
         
     | 
| 
       38 
41 
     | 
    
         | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Meta
         
     | 
| 
      
 2 
     | 
    
         
            +
              include Virtus.model
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              attribute :rating
         
     | 
| 
      
 5 
     | 
    
         
            +
              attribute :have
         
     | 
| 
      
 6 
     | 
    
         
            +
              attribute :want
         
     | 
| 
      
 7 
     | 
    
         
            +
              attribute :formats
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            class Album
         
     | 
| 
      
 11 
     | 
    
         
            +
              include Elasticsearch::Persistence::Model
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              index_name [Rails.application.engine_name, Rails.env].join('-')
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              mapping _parent: { type: 'artist' } do
         
     | 
| 
      
 16 
     | 
    
         
            +
                indexes :suggest_title, type: 'completion', payloads: true
         
     | 
| 
      
 17 
     | 
    
         
            +
                indexes :suggest_track, type: 'completion', payloads: true
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              attribute :artist
         
     | 
| 
      
 21 
     | 
    
         
            +
              attribute :artist_id, String, mapping: { index: 'not_analyzed' }
         
     | 
| 
      
 22 
     | 
    
         
            +
              attribute :label, Hash, mapping: { type: 'object' }
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              attribute :title
         
     | 
| 
      
 25 
     | 
    
         
            +
              attribute :suggest_title, String, default: {}, mapping: { type: 'completion', payloads: true }
         
     | 
| 
      
 26 
     | 
    
         
            +
              attribute :released, Date
         
     | 
| 
      
 27 
     | 
    
         
            +
              attribute :notes
         
     | 
| 
      
 28 
     | 
    
         
            +
              attribute :uri
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              attribute :tracklist, Array, mapping: { type: 'object' }
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              attribute :styles
         
     | 
| 
      
 33 
     | 
    
         
            +
              attribute :meta, Meta, mapping: { type: 'object' }
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Artist
         
     | 
| 
      
 2 
     | 
    
         
            +
              include Elasticsearch::Persistence::Model
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              index_name [Rails.application.engine_name, Rails.env].join('-')
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              analyzed_and_raw = { fields: {
         
     | 
| 
      
 7 
     | 
    
         
            +
                name: { type: 'string', analyzer: 'snowball' },
         
     | 
| 
      
 8 
     | 
    
         
            +
                raw:  { type: 'string', analyzer: 'keyword' }
         
     | 
| 
      
 9 
     | 
    
         
            +
              } }
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              attribute :name, String, mapping: analyzed_and_raw
         
     | 
| 
      
 12 
     | 
    
         
            +
              attribute :suggest_name, String, default: {}, mapping: { type: 'completion', payloads: true }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              attribute :profile
         
     | 
| 
      
 15 
     | 
    
         
            +
              attribute :date, Date
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              attribute :members, String, default: [], mapping: analyzed_and_raw
         
     | 
| 
      
 18 
     | 
    
         
            +
              attribute :members_combined, String, default: [], mapping: { analyzer: 'snowball' }
         
     | 
| 
      
 19 
     | 
    
         
            +
              attribute :suggest_member, String, default: {}, mapping: { type: 'completion', payloads: true }
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              attribute :urls, String, default: []
         
     | 
| 
      
 22 
     | 
    
         
            +
              attribute :album_count, Integer, default: 0
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              validates :name, presence: true
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              def albums
         
     | 
| 
      
 27 
     | 
    
         
            +
                Album.search(
         
     | 
| 
      
 28 
     | 
    
         
            +
                  { query: {
         
     | 
| 
      
 29 
     | 
    
         
            +
                      has_parent: {
         
     | 
| 
      
 30 
     | 
    
         
            +
                        type: 'artist',
         
     | 
| 
      
 31 
     | 
    
         
            +
                        query: {
         
     | 
| 
      
 32 
     | 
    
         
            +
                          filtered: {
         
     | 
| 
      
 33 
     | 
    
         
            +
                            filter: {
         
     | 
| 
      
 34 
     | 
    
         
            +
                              ids: { values: [ self.id ] }
         
     | 
| 
      
 35 
     | 
    
         
            +
                            }
         
     | 
| 
      
 36 
     | 
    
         
            +
                          }
         
     | 
| 
      
 37 
     | 
    
         
            +
                        }
         
     | 
| 
      
 38 
     | 
    
         
            +
                      }
         
     | 
| 
      
 39 
     | 
    
         
            +
                    },
         
     | 
| 
      
 40 
     | 
    
         
            +
                    sort: 'released',
         
     | 
| 
      
 41 
     | 
    
         
            +
                    size: 100
         
     | 
| 
      
 42 
     | 
    
         
            +
                  },
         
     | 
| 
      
 43 
     | 
    
         
            +
                  { type: 'album' }
         
     | 
| 
      
 44 
     | 
    
         
            +
              )
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              def to_param
         
     | 
| 
      
 48 
     | 
    
         
            +
                [id, name.parameterize].join('-')
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,8 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            <%= simple_form_for @artist do |f| %>
         
     | 
| 
      
 2 
     | 
    
         
            +
              <%= f.input :name %>
         
     | 
| 
      
 3 
     | 
    
         
            +
              <%= f.input :profile, as: :text %>
         
     | 
| 
      
 4 
     | 
    
         
            +
              <%= f.input :date, as: :date %>
         
     | 
| 
      
 5 
     | 
    
         
            +
              <%= f.input :members, hint: 'Separate names by comma', input_html: { value: f.object.members.join(', ') } %>
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              <%= f.button :submit %>
         
     | 
| 
      
 8 
     | 
    
         
            +
            <% end %>
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class ArtistsController < ApplicationController
         
     | 
| 
      
 2 
     | 
    
         
            +
              before_action :set_artist, only: [:show, :edit, :update, :destroy]
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              rescue_from Elasticsearch::Persistence::Repository::DocumentNotFound do
         
     | 
| 
      
 5 
     | 
    
         
            +
                render file: "public/404.html", status: 404, layout: false
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def index
         
     | 
| 
      
 9 
     | 
    
         
            +
                @artists = Artist.all sort: 'name.raw', _source: ['name', 'album_count']
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def show
         
     | 
| 
      
 13 
     | 
    
         
            +
                @albums = @artist.albums
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def new
         
     | 
| 
      
 17 
     | 
    
         
            +
                @artist = Artist.new
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def edit
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def create
         
     | 
| 
      
 24 
     | 
    
         
            +
                @artist = Artist.new(artist_params)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                respond_to do |format|
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if @artist.save refresh: true
         
     | 
| 
      
 28 
     | 
    
         
            +
                    format.html { redirect_to @artist, notice: 'Artist was successfully created.' }
         
     | 
| 
      
 29 
     | 
    
         
            +
                    format.json { render :show, status: :created, location: @artist }
         
     | 
| 
      
 30 
     | 
    
         
            +
                  else
         
     | 
| 
      
 31 
     | 
    
         
            +
                    format.html { render :new }
         
     | 
| 
      
 32 
     | 
    
         
            +
                    format.json { render json: @artist.errors, status: :unprocessable_entity }
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              def update
         
     | 
| 
      
 38 
     | 
    
         
            +
                respond_to do |format|
         
     | 
| 
      
 39 
     | 
    
         
            +
                  if @artist.update(artist_params, refresh: true)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    format.html { redirect_to @artist, notice: 'Artist was successfully updated.' }
         
     | 
| 
      
 41 
     | 
    
         
            +
                    format.json { render :show, status: :ok, location: @artist }
         
     | 
| 
      
 42 
     | 
    
         
            +
                  else
         
     | 
| 
      
 43 
     | 
    
         
            +
                    format.html { render :edit }
         
     | 
| 
      
 44 
     | 
    
         
            +
                    format.json { render json: @artist.errors, status: :unprocessable_entity }
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              def destroy
         
     | 
| 
      
 50 
     | 
    
         
            +
                @artist.destroy refresh: true
         
     | 
| 
      
 51 
     | 
    
         
            +
                respond_to do |format|
         
     | 
| 
      
 52 
     | 
    
         
            +
                  format.html { redirect_to artists_url, notice: 'Artist was successfully destroyed.' }
         
     | 
| 
      
 53 
     | 
    
         
            +
                  format.json { head :no_content }
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              private
         
     | 
| 
      
 58 
     | 
    
         
            +
                def set_artist
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @artist = Artist.find(params[:id].split('-').first)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def artist_params
         
     | 
| 
      
 63 
     | 
    
         
            +
                  a = params.require(:artist)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  a[:members] = a[:members].split(/,\s?/) unless a[:members].is_a?(Array) || a[:members].blank?
         
     | 
| 
      
 65 
     | 
    
         
            +
                  return a
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     |