ladder 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9239651dbb526c85d77203a89abcaf134ad353a4
4
- data.tar.gz: e6c5b9a8bf16d50899a9d1ba9b5457d4bbd6c47b
3
+ metadata.gz: e349ffdb2f8ef8f274a8f9f00e0b45c3f46b4a70
4
+ data.tar.gz: 1c2122ef4ef2b18dc2ff74ac2ce59a7e5d5e38fa
5
5
  SHA512:
6
- metadata.gz: d05d9401849734c2292ad8a4d0e1caf252132ee75b76c5a8941c440e0c54297097a75bf6bdbee5f5509fad19eb0ba511ad4284bfb5d55b4ac190b08033898d5d
7
- data.tar.gz: 2fa63d70f71f806d83212032b1acde913963ccdf07e4336ee05ab6a0d0b86aea45e2f1866dbc226a2ae3081cd04a3dc88d68def4952a559d1d64527ec77ad074
6
+ metadata.gz: 63cb325392cdd3525780f8a46c75c5c0a7e70b94521bb23aea92a44e984fdcdc3596304609502add4911e80c22c369b409d2a8f4803ab3196510369e9d01573c
7
+ data.tar.gz: b3654ba928cc613c5eb12955c1c29593215434de4a2cede575b4d6d3788fda62b715677f3c6b85ebae52558d5c38d33f8d44319621aa96bc37af636e80b66672
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  cache: bundler
3
- sudo: false
3
+ #sudo: false
4
4
  rvm:
5
5
  - 1.9.3
6
6
  - 2.1.1
@@ -13,4 +13,6 @@ services:
13
13
  - mongodb
14
14
  - elasticsearch
15
15
  before_script:
16
+ - sudo /usr/share/elasticsearch/bin/plugin -install elasticsearch/elasticsearch-mapper-attachments/2.4.1
17
+ - sudo service elasticsearch restart
16
18
  - sleep 15
data/README.md CHANGED
@@ -1,24 +1,23 @@
1
1
  ![Ladder logo](https://github.com/ladder/ladder/blob/master/logo.png)
2
2
 
3
- [![Gem Version](http://img.shields.io/gem/v/ladder.svg)](https://rubygems.org/gems/ladder) [![Build Status](https://travis-ci.org/ladder/ladder.svg)](https://travis-ci.org/ladder/ladder)
3
+ [![Gem Version](http://img.shields.io/gem/v/ladder.svg)](https://rubygems.org/gems/ladder) [![Inline docs](http://inch-ci.org/github/ladder/ladder.svg?branch=master)](http://inch-ci.org/github/ladder/ladder) [![Build Status](https://travis-ci.org/ladder/ladder.svg)](https://travis-ci.org/ladder/ladder)
4
4
 
5
5
  # Ladder
6
6
 
7
- Ladder is a dynamic [Linked Data](http://en.wikipedia.org/wiki/Linked_data) framework for ActiveModel implemented as a series of Ruby modules that can be used individually and incorporated within existing frameworks (eg. [Project Hydra](http://projecthydra.org)), or combined as a comprehensive stack.
7
+ Ladder is a dynamic framework for [Linked Data](http://en.wikipedia.org/wiki/Linked_data) modelling, persistence, and full-text indexing. It is implemented as a series of Ruby modules that can be used individually and incorporated within existing ActiveModel frameworks (eg. [Project Hydra](http://projecthydra.org)), or combined as a comprehensive stack.
8
8
 
9
- ## History
9
+ Ladder is intended to encourage the [GLAM](http://en.wikipedia.org/wiki/GLAM_(industry_sector)) community to think less dogmatically about established (often monolithic and/or niche) tools and instead embrace a broader vision of adopting more widely-used technologies.
10
10
 
11
- Ladder was loosely conceived over the course of several years prior to 2011. In early 2012, Ladder began existence as an opportunity to escape from a decade of LAMP development and become familiar with Ruby. From 2012 to late 2013, a closed prototype was built under the auspices of [Deliberate Data](http://deliberatedata.com) as a proof-of-concept to test the feasibility of the design. Ladder is intended to encourage the [GLAM](http://en.wikipedia.org/wiki/GLAM_(industry_sector)) community to think less dogmatically about established (often monolithic and/or niche) tools and instead embrace a broader vision of adopting more widely-used technologies.
11
+ ### Components
12
12
 
13
- For those interested in the historical code, the original [prototype](https://github.com/ladder/ladder/tree/prototype) branch is available, as is an [experimental](https://github.com/ladder/ladder/tree/l2) branch.
13
+ - [Mongoid](http://mongoid.org) for persistence
14
+ - [ElasticSearch](http://www.elasticsearch.org) for full-text indexing
15
+ - [ActiveTriples](https://github.com/no-reply/ActiveTriples) for linked data
16
+ - [ActiveJob](https://github.com/rails/rails/tree/master/activejob) for background job execution
14
17
 
15
- ## Components
18
+ ## History
16
19
 
17
- - Persistence ([Mongoid](http://mongoid.org)/MongoDB)
18
- - Full-text indexing ([ElasticSearch](http://www.elasticsearch.org))
19
- - RDF ([ActiveTriples](https://github.com/no-reply/ActiveTriples)/RDF.rb)
20
- - Asynchronous job execution ([Sidekiq](http://sidekiq.org)/Redis)
21
- - HTTP interaction ([Padrino](http://www.padrinorb.com)/Sinatra)
20
+ Ladder was loosely conceived over the course of several years prior to 2011. In early 2012, Ladder began existence as an opportunity to escape from a decade of LAMP development and become familiar with Ruby. From 2012 to late 2013, a closed prototype was built under the auspices of [Deliberate Data](http://deliberatedata.com) as a proof-of-concept to test the feasibility of the design. For those interested in the historical code, the original [prototype](https://github.com/ladder/ladder/tree/prototype) branch is available, as is an [experimental](https://github.com/ladder/ladder/tree/l2) branch.
22
21
 
23
22
  ## Installation
24
23
 
@@ -41,9 +40,11 @@ Or install it yourself as:
41
40
  * [Resources](#resources)
42
41
  * [Configuring Resources](#configuring-resources)
43
42
  * [Dynamic Resources](#dynamic-resources)
44
- * [Indexing for Search](#indexing-for-search)
45
43
  * [Files](#files)
44
+ * [Indexing](#indexing)
45
+ * [Indexing Resources](#indexing-resources)
46
46
  * [Indexing Files](#indexing-files)
47
+ * [Background Indexing](#background-indexing)
47
48
 
48
49
  ### Resources
49
50
 
@@ -341,35 +342,101 @@ steve.as_jsonld
341
342
  # }
342
343
  ```
343
344
 
344
- Note that due to the way Mongoid handles dynamic fields, dynamic properties **can not** be localized. They can be any kind of literal, but they **can not** be a related object. They can, however, contain a reference to the related object's URI.
345
+ Note that due to the way Mongoid handles dynamic fields, dynamic properties **can not** be localized. They can be any kind of literal, but they **can not** be a related object. They can, however, contain the related object's URI.
345
346
 
346
- ### Indexing for Search
347
+ ### Files
347
348
 
348
- You can also index your model classes for keyword searching through ElasticSearch by mixing in the Ladder::Searchable module:
349
+ Files are bytestreams that store binary content using MongoDB's GridFS storage system. They are still identifiable by a URI, and contain technical metadata about the File's contents.
349
350
 
350
351
  ```ruby
351
352
  class Person
352
353
  include Ladder::Resource
353
- include Ladder::Searchable
354
354
 
355
355
  configure type: RDF::FOAF.Person
356
356
 
357
357
  property :first_name, predicate: RDF::FOAF.name
358
- property :description, predicate: RDF::DC.description
358
+ property :thumbnails, predicate: RDF::FOAF.depiction, class_name: 'Image', inverse_of: nil
359
359
  end
360
360
 
361
- kimchy = Person.new(first_name: 'Shay', description: 'Real genius')
362
- => #<Person _id: 543b457b41697231c5000000, first_name: {"en"=>"Shay"}, description: {"en"=>"Real genius"}>
361
+ class Image
362
+ include Ladder::File
363
+ end
363
364
  ```
364
365
 
365
- In order to enable indexing, call the `#index_for_search` method on the class:
366
+ Similar to Resources, using `#property` will create a has-many relation for a File by default; however, because Files must be the target of a one-way relation, the `inverse_of: nil` option is required. Note that due to the way GridFS is designed, Files **can not** be embedded.
366
367
 
367
368
  ```ruby
368
- Person.index_for_search
369
- => :as_indexed_json
369
+ steve = Person.new(first_name: 'Steve')
370
+ => #<Person _id: 549d83c64169720b32010000, first_name: {"en"=>"Steve"}>
370
371
 
371
- kimchy.as_indexed_json
372
- => {"description"=>"Real genius", "first_name"=>"Shay"}
372
+ thumb = Image.new(file: open('http://some.image/pic.jpg'))
373
+ => #<Image _id: 549d83c24169720b32000000>
374
+
375
+ steve.thumbnails << thumb
376
+ => [#<Image _id: 549d83c24169720b32000000, >]
377
+
378
+ steve.as_jsonld
379
+ # => {
380
+ # "@context": {
381
+ # "foaf": "http://xmlns.com/foaf/0.1/"
382
+ # },
383
+ # "@id": "http://example.org/people/549d83c64169720b32010000",
384
+ # "@type": "foaf:Person",
385
+ # "foaf:depiction": {
386
+ # "@id": "http://example.org/images/549d83c24169720b32000000"
387
+ # },
388
+ # "foaf:name": {
389
+ # "@language": "en",
390
+ # "@value": "Steve"
391
+ # }
392
+ # }
393
+
394
+ steve.save
395
+ # ... File is stored to GridFS ...
396
+ => true
397
+ ```
398
+
399
+ Files have all the attributes of a GridFS file, and the stored binary content is accessed using `#data`.
400
+
401
+ ```ruby
402
+ thumb.reload
403
+ => #<Image _id: 549d86184169720b6a000000, >
404
+
405
+ thumb.as_document
406
+ => {"_id"=>BSON::ObjectId('549d86184169720b6a000000'),
407
+ "length"=>59709,
408
+ "chunkSize"=>4194304,
409
+ "uploadDate"=>2014-12-26 16:00:29 UTC,
410
+ "md5"=>"0d4a486e2cd71c51b7a92cfe96f29324",
411
+ "contentType"=>"image/jpeg",
412
+ "filename"=>"549d86184169720b6a000000/open-uri20141226-2922-u66ap6"}
413
+
414
+ thumb.length
415
+ => 59709
416
+
417
+ thumb.data
418
+ => # ... binary data ...
419
+ ```
420
+
421
+ ### Indexing
422
+
423
+ #### Indexing Resources
424
+
425
+ You can index Resources for keyword searching by mixing in the Ladder::Searchable module:
426
+
427
+ ```ruby
428
+ class Person
429
+ include Ladder::Resource
430
+ include Ladder::Searchable
431
+
432
+ configure type: RDF::FOAF.Person
433
+
434
+ property :first_name, predicate: RDF::FOAF.name
435
+ property :description, predicate: RDF::DC.description
436
+ end
437
+
438
+ kimchy = Person.new(first_name: 'Shay', description: 'Real genius')
439
+ => #<Person _id: 543b457b41697231c5000000, first_name: {"en"=>"Shay"}, description: {"en"=>"Real genius"}>
373
440
 
374
441
  kimchy.save
375
442
  => true
@@ -393,10 +460,10 @@ results.records.first == kimchy
393
460
  => true
394
461
  ```
395
462
 
396
- When indexing, you can control how your model is stored in the index by supplying the `as: :jsonld` or `as: :qname` options:
463
+ When indexing, you can control how your model is stored in the index by calling `#index_for_search` and supplying a block that returns a serializable hash:
397
464
 
398
465
  ```ruby
399
- Person.index_for_search as: :jsonld
466
+ Person.index_for_search { as_jsonld }
400
467
  => :as_indexed_json
401
468
 
402
469
  kimchy.as_indexed_json
@@ -417,7 +484,7 @@ kimchy.as_indexed_json
417
484
  # }
418
485
  # }
419
486
 
420
- Person.index_for_search as: :qname
487
+ Person.index_for_search { as_qname }
421
488
  => :as_indexed_json
422
489
 
423
490
  kimchy.as_indexed_json
@@ -434,7 +501,7 @@ kimchy.as_indexed_json
434
501
  # }
435
502
  ```
436
503
 
437
- You can also index related objects as framed JSON-LD or hierarchical qname, by again using the `related: true` option:
504
+ You can also index related objects as framed JSON-LD using `#as_framed_jsonld` or by using the `related: true` option with `#as_qname`:
438
505
 
439
506
  ```ruby
440
507
  class Project
@@ -456,10 +523,10 @@ es = Project.new(project_name: 'ElasticSearch', description: 'You know, for sear
456
523
  es.developers << kimchy
457
524
  => [#<Person _id: 543b457b41697231c5000000, first_name: {"en"=>"Shay"}, description: {"en"=>"Real genius"}, project_ids: [BSON::ObjectId('544562c24169728b4e010000')]>]
458
525
 
459
- Person.index_for_search as: :jsonld, related: true
526
+ Person.index_for_search { as_framed_jsonld }
460
527
  => :as_indexed_json
461
528
 
462
- Project.index_for_search as: :jsonld, related: true
529
+ Project.index_for_search { as_framed_jsonld }
463
530
  => :as_indexed_json
464
531
 
465
532
  kimchy.as_indexed_json
@@ -530,10 +597,10 @@ es.as_indexed_json
530
597
  # }
531
598
  # }
532
599
 
533
- Person.index_for_search as: :qname, related: true
600
+ Person.index_for_search { as_qname related: true }
534
601
  => :as_indexed_json
535
602
 
536
- Project.index_for_search as: :qname, related: true
603
+ Project.index_for_search { as_qname related: true }
537
604
  => :as_indexed_json
538
605
 
539
606
  kimchy.as_indexed_json
@@ -587,83 +654,9 @@ es.as_indexed_json
587
654
  # }
588
655
  ```
589
656
 
590
- ### Files
591
-
592
- Files are bytestreams that store binary content using MongoDB's GridFS storage system. They are still identifiable by a URI, and contain technical metadata about the File's contents.
593
-
594
- ```ruby
595
- class Person
596
- include Ladder::Resource
597
-
598
- configure type: RDF::FOAF.Person
599
-
600
- property :first_name, predicate: RDF::FOAF.name
601
- property :thumbnails, predicate: RDF::FOAF.depiction, class_name: 'Image', inverse_of: nil
602
- end
603
-
604
- class Image
605
- include Ladder::File
606
- end
607
- ```
608
-
609
- Similar to Resources, using `#property` as above will create a has-many relation for a File by default; however, because Files must be the target of a one-way relation, the `inverse_of: nil` option is required. Note that due to the way GridFS is designed, Files **can not** be embedded.
610
-
611
- ```ruby
612
- steve = Person.new(first_name: 'Steve')
613
- => #<Person _id: 549d83c64169720b32010000, first_name: {"en"=>"Steve"}>
614
-
615
- thumb = Image.new(file: open('http://some.image/pic.jpg'))
616
- => #<Image _id: 549d83c24169720b32000000>
617
-
618
- steve.thumbnails << thumb
619
- => [#<Image _id: 549d83c24169720b32000000, >]
620
-
621
- steve.as_jsonld
622
- # => {
623
- # "@context": {
624
- # "foaf": "http://xmlns.com/foaf/0.1/"
625
- # },
626
- # "@id": "http://example.org/people/549d83c64169720b32010000",
627
- # "@type": "foaf:Person",
628
- # "foaf:depiction": {
629
- # "@id": "http://example.org/images/549d83c24169720b32000000"
630
- # },
631
- # "foaf:name": {
632
- # "@language": "en",
633
- # "@value": "Steve"
634
- # }
635
- # }
636
-
637
- steve.save
638
- # ... File is stored to GridFS ...
639
- => true
640
- ```
641
-
642
- Files have all the attributes of a GridFS file, and the stored binary content is accessed using `#data`.
643
-
644
- ```ruby
645
- thumb.reload
646
- => #<Image _id: 549d86184169720b6a000000, >
647
-
648
- thumb.as_document
649
- => {"_id"=>BSON::ObjectId('549d86184169720b6a000000'),
650
- "length"=>59709,
651
- "chunkSize"=>4194304,
652
- "uploadDate"=>2014-12-26 16:00:29 UTC,
653
- "md5"=>"0d4a486e2cd71c51b7a92cfe96f29324",
654
- "contentType"=>"image/jpeg",
655
- "filename"=>"549d86184169720b6a000000/open-uri20141226-2922-u66ap6"}
656
-
657
- thumb.length
658
- => 59709
659
-
660
- thumb.data
661
- => # ... binary data ...
662
- ```
663
-
664
657
  #### Indexing Files
665
658
 
666
- Files that contain textual content (eg. HTML, PDF, ePub, DOC, etc) can be automatically indexed when they are persisted, again just by mixing in the Ladder::Searchable module (there is no need to call `#index_for_search` on the class). Note that this requires the [Mapper Attachments Plugin for Elasticsearch](https://github.com/elasticsearch/elasticsearch-mapper-attachments) to be installed.
659
+ Files that contain textual content (eg. HTML, PDF, ePub, DOC, etc) can be indexed when they are stored, again by mixing in the Ladder::Searchable module. This can be useful if you want to retrieve a File by searching for the textual content that it contains. Note that this requires the [Mapper Attachments Plugin for Elasticsearch](https://github.com/elasticsearch/elasticsearch-mapper-attachments) to be installed.
667
660
 
668
661
  ```ruby
669
662
  class OCR
@@ -705,7 +698,7 @@ results.records.first.data
705
698
  => # ... binary data ...
706
699
  ```
707
700
 
708
- This can be useful if you want to retrieve a File by searching for the textual content that it contains. Note the use of `#records` to access the Ladder::File instances directly ([see here for more information](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-model#search-results-as-database-records)). However, if you want to get information about the file characteristics (including the extracted textual content), you can use a modified search query:
701
+ Note the use of `#records` to access the Ladder::File instances directly ([see here for more information](https://github.com/elasticsearch/elasticsearch-rails/tree/master/elasticsearch-model#search-results-as-database-records)). However, if you want to get information about the file characteristics (including the extracted textual content), you can use a modified search query:
709
702
 
710
703
  ```ruby
711
704
  results = OCR.search 'Moomintroll', fields: '*'
@@ -754,7 +747,37 @@ results.first.highlight.file
754
747
  ", but at the same time <em>his</em> nose caught a new smell. It was a more \nserious smell than any he had met"]
755
748
  ```
756
749
 
757
- More information about performing highlighting queries is available in the [Elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html).
750
+ More information about highlighting queries is available in the [Elasticsearch documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html).
751
+
752
+ #### Background Indexing
753
+
754
+ In large-scale production environments, sending an HTTP request to Elasticsearch during the database transaction isn't optimal (especially for large Files), so Ladder uses ActiveJob to queue and process indexing operations in the background. Just use the Ladder::Searchable::Background module in your model:
755
+
756
+ ```ruby
757
+ class OCR
758
+ include Ladder::File
759
+ include Ladder::Searchable::Background
760
+ end
761
+
762
+ # ...
763
+
764
+ class Person
765
+ include Ladder::Resource
766
+ include Ladder::Searchable::Background
767
+
768
+ configure type: RDF::FOAF.Person
769
+ end
770
+
771
+ # ...
772
+ ```
773
+
774
+ You'll also have to set the queue adapter in your application, depending on which backend you're using:
775
+
776
+ ```ruby
777
+ ActiveJob::Base.queue_adapter = :sidekiq
778
+ ```
779
+
780
+ For more information on available queueing adapters and their features, see the [ActiveJob documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
758
781
 
759
782
  ## Contributing
760
783
 
@@ -779,4 +802,4 @@ Many thanks to Christopher Knight [@NomadicKnight](https://twitter.com/Nomadic_K
779
802
  ## License
780
803
 
781
804
  Apache License Version 2.0
782
- http://apache.org/licenses/LICENSE-2.0.txt
805
+ http://apache.org/licenses/LICENSE-2.0.txt
data/ladder.gemspec CHANGED
@@ -20,17 +20,18 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ["lib"]
22
22
 
23
+ spec.add_dependency "active-triples", "~> 0.6"
24
+ spec.add_dependency "activejob", "~> 4.2"
25
+ spec.add_dependency "elasticsearch-model", "~> 0.1"
23
26
  spec.add_dependency "mongoid", "~> 4.0"
24
27
  spec.add_dependency "mongoid-grid_fs", "~> 2.1"
25
- spec.add_dependency "active-triples", "~> 0.4"
26
- spec.add_dependency "elasticsearch-model", "~> 0.1"
27
- spec.add_dependency "mimemagic", "~> 0.2"
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 1.7"
30
+ spec.add_development_dependency "mimemagic", "~> 0.2"
30
31
  spec.add_development_dependency "pry", "~> 0.10"
31
- spec.add_development_dependency "wirble", "~> 0.1"
32
32
  spec.add_development_dependency "rspec", "~> 3.1"
33
33
  spec.add_development_dependency "rake", "~> 10.4"
34
- spec.add_development_dependency "yard", "~> 0.8"
35
34
  spec.add_development_dependency "simplecov", "~> 0.9"
35
+ spec.add_development_dependency "wirble", "~> 0.1"
36
+ spec.add_development_dependency "yard", "~> 0.8"
36
37
  end
data/lib/ladder/file.rb CHANGED
@@ -10,7 +10,7 @@ module Ladder::File
10
10
  included do
11
11
  configure base_uri: RDF::URI.new(LADDER_BASE_URI) / name.underscore.pluralize if defined? LADDER_BASE_URI
12
12
 
13
- store_in :collection => "#{ grid.prefix }.files"
13
+ store_in collection: "#{ grid.prefix }.files"
14
14
 
15
15
  # Define accessor methods for attributes
16
16
  define_method(:content_type) { read_attribute(:contentType) }
@@ -18,24 +18,12 @@ module Ladder::File
18
18
  grid::File.fields.keys.map(&:to_sym).each do |attr|
19
19
  define_method(attr) { read_attribute(attr) }
20
20
  end
21
- end
22
-
23
- attr_accessor :file
24
-
25
- ##
26
- # Make save behave like Mongoid::Document as much as possible
27
- def save
28
- # FIXME: this raises on a freshly-retrieved document; should either set @file or catch @grid_file
29
- raise Mongoid::Errors::InvalidValue.new(IO, NilClass) if file.nil?
30
21
 
31
- persisted? ? run_callbacks(:update) : run_callbacks(:create)
32
-
33
- run_callbacks(:save) do
34
- attributes[:content_type] = file.content_type if file.respond_to? :content_type
35
- @grid_file ? @grid_file.save : !! @grid_file = self.class.grid.put(file, attributes.symbolize_keys)
36
- end
22
+ around_save :save_file
37
23
  end
38
24
 
25
+ attr_accessor :file
26
+
39
27
  ##
40
28
  # Output content of object from stored file or readable input
41
29
  def data
@@ -51,6 +39,17 @@ module Ladder::File
51
39
  def update_resource
52
40
  resource
53
41
  end
42
+
43
+ private
44
+
45
+ ##
46
+ # Make save behave like Mongoid::Document as much as possible
47
+ def save_file(&block)
48
+ attributes[:content_type] = file.content_type if file.respond_to? :content_type
49
+ @grid_file ? @grid_file.save : !! @grid_file = self.class.grid.put(file, attributes.symbolize_keys)
50
+
51
+ persisted? ? run_callbacks(:update) : run_callbacks(:create)
52
+ end
54
53
 
55
54
  module ClassMethods
56
55
  ##
@@ -58,7 +57,6 @@ module Ladder::File
58
57
  def grid
59
58
  @grid ||= Mongoid::GridFs.build_namespace_for name
60
59
  end
61
-
62
60
  end
63
61
 
64
62
  end