Skip to content

Commit e3a4414

Browse files
committed
[STORE] Added full readme for the "active record" pattern
1 parent 1fcc0a2 commit e3a4414

File tree

2 files changed

+195
-4
lines changed

2 files changed

+195
-4
lines changed

elasticsearch-persistence/README.md

+176-4
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,182 @@ and demonstrates a rich set of features of the repository.
423423

424424
### The ActiveRecord Pattern
425425

426-
[_Work in progress_](https://github.com/elasticsearch/elasticsearch-rails/pull/91).
427-
The ActiveRecord [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html) will work
428-
in a very similar way as `Tire::Model::Persistence`, allowing a drop-in replacement of
429-
an Elasticsearch-backed model in Ruby on Rails applications.
426+
The `Elasticsearch::Persistence::Model` module provides an implementation of the
427+
active record [pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html),
428+
with a familiar interface for using Elasticsearch as a persistence layer in
429+
Ruby on Rails applications.
430+
431+
All the methods are documented with comprehensive examples in the source code,
432+
available also online at <http://rubydoc.info/gems/elasticsearch-persistence/Elasticsearch/Persistence/Model>.
433+
434+
#### Model Definition
435+
436+
The integration is implemented by including the module in a Ruby class.
437+
The model attribute definition support is implemented with the
438+
[_Virtus_](https://github.com/solnic/virtus) Rubygem, and the
439+
naming, validation, etc. features with the
440+
[_ActiveModel_](https://github.com/rails/rails/tree/master/activemodel) Rubygem.
441+
442+
```ruby
443+
class Article
444+
include Elasticsearch::Persistence::Model
445+
446+
# Define a plain `title` attribute
447+
#
448+
attribute :title, String
449+
450+
# Define an `author` attribute, with multiple analyzers for this field
451+
#
452+
attribute :author, String, mapping: { fields: {
453+
author: { type: 'string'},
454+
raw: { type: 'string', analyzer: 'keyword' }
455+
} }
456+
457+
458+
# Define a `views` attribute, with default value
459+
#
460+
attribute :views, Integer, default: 0, mapping: { type: 'integer' }
461+
462+
# Validate the presence of the `title` attribute
463+
#
464+
validates :title, presence: true
465+
466+
# Execute code after saving the model.
467+
#
468+
after_save { puts "Successfuly saved: #{self}" }
469+
end
470+
```
471+
472+
Attribute validations works like for any other _ActiveModel_-compatible implementation:
473+
474+
```ruby
475+
article = Article.new # => #<Article { ... }>
476+
477+
article.valid?
478+
# => false
479+
480+
article.errors.to_a
481+
# => ["Title can't be blank"]
482+
```
483+
484+
#### Persistence
485+
486+
We can create a new article in the database...
487+
488+
```ruby
489+
Article.create id: 1, title: 'Test', author: 'John'
490+
# PUT http://localhost:9200/articles/article/1 [status:201, request:0.015s, query:n/a]
491+
```
492+
493+
... and find it:
494+
495+
```ruby
496+
article = Article.find(1)
497+
# => #<Article { ... }>
498+
499+
article._index
500+
# => "articles"
501+
502+
article.id
503+
# => "1"
504+
505+
article.title
506+
# => "Test"
507+
```
508+
509+
To update the model, either update the attribute and save the model:
510+
511+
```ruby
512+
article.title = 'Updated'
513+
514+
article.save
515+
=> {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>2, "created"=>false}
516+
```
517+
518+
... or use the `update_attributes` method:
519+
520+
```ruby
521+
article.update_attributes title: 'Test', author: 'Mary'
522+
# => {"_index"=>"articles", "_type"=>"article", "_id"=>"1", "_version"=>3}
523+
```
524+
525+
The implementation supports the familiar interface for updating model timestamps:
526+
527+
```ruby
528+
article.touch
529+
# => => { ... "_version"=>4}
530+
```
531+
532+
... and numeric attributes:
533+
534+
```ruby
535+
article.views
536+
# => 0
537+
538+
article.increment :views
539+
article.views
540+
# => 1
541+
```
542+
543+
Any callbacks defined in the model will be triggered during the persistence operations:
544+
545+
```ruby
546+
article.save
547+
# Successfuly saved: #<Article {...}>
548+
```
549+
550+
The model also supports familiar `find_in_batches` and `find_each` methods to efficiently
551+
retrieve big collections of model instance, using the Elasticsearch's _Scan API_:
552+
553+
```ruby
554+
Article.find_each(_source_include: 'title') { |a| puts "===> #{a.title.upcase}" }
555+
# GET http://localhost:9200/articles/article/_search?scroll=5m&search_type=scan&size=20
556+
# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
557+
# ===> TEST
558+
# GET http://localhost:9200/_search/scroll?scroll=5m&scroll_id=c2Nhb...
559+
# => "c2Nhb..."
560+
```
561+
562+
#### Search
563+
564+
The model class provides a `search` method to retrieve model instances with a regular
565+
search definition, including highlighting, aggregations, etc:
566+
567+
```ruby
568+
results = Article.search query: { match: { title: 'test' } },
569+
aggregations: { authors: { terms: { field: 'author.raw' } } },
570+
highlight: { fields: { title: {} } }
571+
572+
puts results.first.title
573+
# Test
574+
575+
puts results.first.hit.highlight['title']
576+
# <em>Test</em>
577+
578+
puts results.response.aggregations.authors.buckets.each { |b| puts "#{b['key']} : #{b['doc_count']}" }
579+
# John : 1
580+
```
581+
582+
#### Rails Compatibility
583+
584+
The model instances are fully compatible with Rails' conventions and helpers:
585+
586+
```ruby
587+
url_for article
588+
# => "http://localhost:3000/articles/1"
589+
590+
div_for article
591+
# => '<div class="article" id="article_1"></div>'
592+
```
593+
594+
... as well as form values for dates and times:
595+
596+
```ruby
597+
article = Article.new "title" => "Date", "published(1i)"=>"2014", "published(2i)"=>"1", "published(3i)"=>"1"
598+
599+
article.published.iso8601
600+
# => "2014-01-01"
601+
```
430602

431603
## License
432604

elasticsearch-persistence/lib/elasticsearch/persistence.rb

+19
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,25 @@ module Elasticsearch
5656
# # 2014-04-04 22:15:25 +0200: > {"foo":"bar"}
5757
# # 2014-04-04 22:15:25 +0200: < {"_index":"my_notes","_type":"note","_id":"-d28yXLFSlusnTxb13WIZQ", ...}
5858
#
59+
# == Model
60+
#
61+
# The active record pattern allows to use the interface familiar from ActiveRecord models:
62+
#
63+
# require 'elasticsearch/persistence'
64+
#
65+
# class Article
66+
# attribute :title, String, mapping: { analyzer: 'snowball' }
67+
# end
68+
#
69+
# article = Article.new id: 1, title: 'Test'
70+
# article.save
71+
#
72+
# Article.find(1)
73+
#
74+
# article.update_attributes title: 'Update'
75+
#
76+
# article.destroy
77+
#
5978
module Persistence
6079

6180
# :nodoc:

0 commit comments

Comments
 (0)