ladder 0.2.0 → 0.2.1

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: b9a1843bcc9f4d0ff1d006c83df08c744fb96c93
4
- data.tar.gz: 2c7ed1fbff81db63f50bc3d55ae0999895b409b9
3
+ metadata.gz: 9239651dbb526c85d77203a89abcaf134ad353a4
4
+ data.tar.gz: e6c5b9a8bf16d50899a9d1ba9b5457d4bbd6c47b
5
5
  SHA512:
6
- metadata.gz: c0544b5405bdee76c45d6fe64caa87dad1d4273d54e74bbc7c2779169222ef59570685b0e4d8230e9d2c9abf7965f14c9e4ff43ecb77f18406b53f873fec7a76
7
- data.tar.gz: c962ddc67778d0ab32653ebead42aab4caff5e911850cf0e42adf2898bdfd5acc7a925c126526844221b7832d37ba95f4a7bdc331247b5dce4cf26ab6337eee5
6
+ metadata.gz: d05d9401849734c2292ad8a4d0e1caf252132ee75b76c5a8941c440e0c54297097a75bf6bdbee5f5509fad19eb0ba511ad4284bfb5d55b4ac190b08033898d5d
7
+ data.tar.gz: 2fa63d70f71f806d83212032b1acde913963ccdf07e4336ee05ab6a0d0b86aea45e2f1866dbc226a2ae3081cd04a3dc88d68def4952a559d1d64527ec77ad074
data/README.md CHANGED
@@ -41,13 +41,13 @@ Or install it yourself as:
41
41
  * [Resources](#resources)
42
42
  * [Configuring Resources](#configuring-resources)
43
43
  * [Dynamic Resources](#dynamic-resources)
44
- * [Files](#files)
45
44
  * [Indexing for Search](#indexing-for-search)
45
+ * [Files](#files)
46
+ * [Indexing Files](#indexing-files)
46
47
 
47
48
  ### Resources
48
49
 
49
- Much like ActiveTriples, Resources are the core of Ladder. Resources implement all the functionality of a Mongoid::Document and an ActiveTriples::Resource. To add Ladder integration for your model, require
50
- and include the main module in your class:
50
+ Much like ActiveTriples, Resources are the core of Ladder. Resources implement all the functionality of a Mongoid::Document and an ActiveTriples::Resource. To add Ladder integration for your model, require and include the main module in your class:
51
51
 
52
52
  ```ruby
53
53
  require 'ladder'
@@ -61,12 +61,13 @@ class Person
61
61
  property :description, predicate: RDF::DC.description
62
62
  end
63
63
 
64
- steve = Person.new
65
- steve.first_name = 'Steve'
66
- steve.description = 'Funny-looking'
67
-
64
+ steve = Person.new(first_name: 'Steve', description: 'Funny-looking')
65
+ => #<Person _id: 542f0c124169720ea0000000, first_name: {"en"=>"Steve"}, description: {"en"=>"Funny-looking"}>
66
+
68
67
  steve.as_document
69
- => {"_id"=>BSON::ObjectId('542f0c124169720ea0000000'), "first_name"=>{"en"=>"Steve"}, "description"=>{"en"=>"Funny-looking"}}
68
+ => {"_id"=>BSON::ObjectId('542f0c124169720ea0000000'),
69
+ "first_name"=>{"en"=>"Steve"},
70
+ "description"=>{"en"=>"Funny-looking"}}
70
71
 
71
72
  steve.as_jsonld
72
73
  # => {
@@ -249,8 +250,6 @@ Person.resource_class.base_uri
249
250
  => #<RDF::URI:0x3fecf69da274 URI:http://example.org/people>
250
251
 
251
252
  Person.configure base_uri: 'http://some.other.uri/'
252
-
253
- Person.resource_class.base_uri
254
253
  => "http://some.other.uri/"
255
254
  ```
256
255
 
@@ -268,12 +267,16 @@ class Person
268
267
  end
269
268
 
270
269
  steve = Person.new(first_name: 'Steve')
270
+ => #<Person _id: 546669234169720397000000, first_name: {"en"=>"Steve"}>
271
271
 
272
272
  steve.description
273
273
  => NoMethodError: undefined method 'description' for #<Person:0x007fb54eb1d0b8>
274
274
 
275
275
  steve.property :description, predicate: RDF::DC.description
276
+ => {:description=>"http://purl.org/dc/terms/description"}
277
+
276
278
  steve.description = 'Funny-looking'
279
+ => "Funny-looking"
277
280
 
278
281
  steve.as_document
279
282
  => {"_id"=>BSON::ObjectId('546669234169720397000000'),
@@ -340,73 +343,6 @@ steve.as_jsonld
340
343
 
341
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.
342
345
 
343
- ### Files
344
-
345
- 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.
346
-
347
- ```ruby
348
- class Person
349
- include Ladder::Resource
350
-
351
- configure type: RDF::FOAF.Person
352
-
353
- property :first_name, predicate: RDF::FOAF.name
354
- property :thumbnails, predicate: RDF::FOAF.depiction, class_name: 'Image', inverse_of: nil
355
- end
356
-
357
- class Image
358
- include Ladder::File
359
- end
360
- ```
361
-
362
- 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.
363
-
364
- ```ruby
365
- steve = Person.new(first_name: 'Steve')
366
- thumb = Image.new(file: open('http://some.image/pic.jpg'))
367
- steve.thumbnails << thumb
368
-
369
- steve.as_jsonld
370
- # => {
371
- # "@context": {
372
- # "foaf": "http://xmlns.com/foaf/0.1/"
373
- # },
374
- # "@id": "http://example.org/people/549d83c64169720b32010000",
375
- # "@type": "foaf:Person",
376
- # "foaf:depiction": {
377
- # "@id": "http://example.org/images/549d83c24169720b32000000"
378
- # },
379
- # "foaf:name": {
380
- # "@language": "en",
381
- # "@value": "Steve"
382
- # }
383
- # }
384
-
385
- steve.save
386
- # ... File is stored to GridFS ...
387
- => true
388
- ```
389
-
390
- Files have all the attributes of a GridFS file, and the stored binary content is accessed using `#data`.
391
-
392
- ```ruby
393
- thumb.reload
394
- thumb.as_document
395
- => {"_id"=>BSON::ObjectId('549d86184169720b6a000000'),
396
- "length"=>59709,
397
- "chunkSize"=>4194304,
398
- "uploadDate"=>2014-12-26 16:00:29 UTC,
399
- "md5"=>"0d4a486e2cd71c51b7a92cfe96f29324",
400
- "contentType"=>"image/jpeg",
401
- "filename"=>"549d86184169720b6a000000/open-uri20141226-2922-u66ap6"}
402
-
403
- thumb.length
404
- => 59709
405
-
406
- thumb.data
407
- => # ... binary data ...
408
- ```
409
-
410
346
  ### Indexing for Search
411
347
 
412
348
  You can also index your model classes for keyword searching through ElasticSearch by mixing in the Ladder::Searchable module:
@@ -422,9 +358,8 @@ class Person
422
358
  property :description, predicate: RDF::DC.description
423
359
  end
424
360
 
425
- kimchy = Person.new
426
- kimchy.first_name = 'Shay'
427
- kimchy.description = 'Real genius'
361
+ kimchy = Person.new(first_name: 'Shay', description: 'Real genius')
362
+ => #<Person _id: 543b457b41697231c5000000, first_name: {"en"=>"Shay"}, description: {"en"=>"Real genius"}>
428
363
  ```
429
364
 
430
365
  In order to enable indexing, call the `#index_for_search` method on the class:
@@ -516,10 +451,14 @@ end
516
451
  Person.property :projects, predicate: RDF::FOAF.made, class_name: 'Project'
517
452
 
518
453
  es = Project.new(project_name: 'ElasticSearch', description: 'You know, for search')
454
+ => #<Project _id: 544562c24169728b4e010000, project_name: {"en"=>"ElasticSearch"}, description: {"en"=>"You know, for search"}, developer_ids: nil>
455
+
519
456
  es.developers << kimchy
457
+ => [#<Person _id: 543b457b41697231c5000000, first_name: {"en"=>"Shay"}, description: {"en"=>"Real genius"}, project_ids: [BSON::ObjectId('544562c24169728b4e010000')]>]
520
458
 
521
459
  Person.index_for_search as: :jsonld, related: true
522
460
  => :as_indexed_json
461
+
523
462
  Project.index_for_search as: :jsonld, related: true
524
463
  => :as_indexed_json
525
464
 
@@ -593,6 +532,7 @@ es.as_indexed_json
593
532
 
594
533
  Person.index_for_search as: :qname, related: true
595
534
  => :as_indexed_json
535
+
596
536
  Project.index_for_search as: :qname, related: true
597
537
  => :as_indexed_json
598
538
 
@@ -647,6 +587,175 @@ es.as_indexed_json
647
587
  # }
648
588
  ```
649
589
 
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
+ #### Indexing Files
665
+
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.
667
+
668
+ ```ruby
669
+ class OCR
670
+ include Ladder::File
671
+ include Ladder::Searchable
672
+ end
673
+
674
+ pdf = OCR.new(file: open('http://some.location/ocr.pdf'))
675
+ => #<OCR _id: 54add77a4169721c23000000>
676
+
677
+ pdf.save
678
+ => true
679
+
680
+ results = OCR.search 'Moomintroll'
681
+ # => #<Elasticsearch::Model::Response::Response:0x007fa2ca82a9f0
682
+ # @klass=[PROXY] OCR,
683
+ # @search=
684
+ # #<Elasticsearch::Model::Searching::SearchRequest:0x007fa2ca830a58
685
+ # @definition={:index=>"ocrs", :type=>"ocr", :q=>"Moomintroll"},
686
+ # @klass=[PROXY] OCR,
687
+ # @options={}>>
688
+
689
+ results.count
690
+ => 1
691
+
692
+ results.records.first == pdf
693
+ => true
694
+
695
+ results.records.first.as_document
696
+ => {"_id"=>BSON::ObjectId('54add77a4169721c23000000'),
697
+ "length"=>12941,
698
+ "chunkSize"=>4194304,
699
+ "uploadDate"=>2015-01-08 01:03:54 UTC,
700
+ "md5"=>"831a47b953d6e11d17cee7de9abd73c4",
701
+ "contentType"=>"application/pdf",
702
+ "filename"=>"54add77a4169721c23000000/ocr.pdf"}
703
+
704
+ results.records.first.data
705
+ => # ... binary data ...
706
+ ```
707
+
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:
709
+
710
+ ```ruby
711
+ results = OCR.search 'Moomintroll', fields: '*'
712
+ # => #<Elasticsearch::Model::Response::Response:0x007fc36cadaa20
713
+ # @klass=[PROXY] OCR,
714
+ # @search=
715
+ # #<Elasticsearch::Model::Searching::SearchRequest:0x007fc36cadab10
716
+ # @definition={:index=>"ocrs", :type=>"ocr", :body=>{:query=>{:query_string=>{:query=>"Moomintroll"}}, :fields=>"*"}},
717
+ # @klass=[PROXY] OCR,
718
+ # @options={}>>
719
+
720
+ results.count
721
+ => 1
722
+
723
+ results.first.fields
724
+ => {
725
+ "file.content_type"=>["application/pdf"],
726
+ "file.keywords"=>[""],
727
+ "file"=>
728
+ ["\nAnd so Moomintroll was helplessly thrown out into a strange and dangerous world and \ndropped up to his ears in the first snowdrift of his experience. It felt unpleasantly prickly \nto his velvet skin, but at the same time his nose caught a new smell. It was a more \nserious smell than any he had met before, and slightly frightening. But it made him wide \nawake and greatly interested.\n\n\n"],
729
+ "file.date"=>["2014-12-19T15:32:58Z"],
730
+ "file.title"=>["Untitled"]}
731
+ ```
732
+
733
+ In this case, the `#fields` Hash contains all of the technical metadata obtained by Elasticsearch during indexing. Note that this is **not the same** as the metadata stored by GridFS (with the possible exception of content type). Finally, we can also provide contextual highlighting for search results by using a slightly more complex search query:
734
+
735
+ ```ruby
736
+ results = OCR.search query: { query_string: { query: 'his' } }, highlight: { fields: { file: {} } }
737
+ # => #<Elasticsearch::Model::Response::Response:0x007fd653dc8b48
738
+ # @klass=[PROXY] OCR,
739
+ # @search=
740
+ # #<Elasticsearch::Model::Searching::SearchRequest:0x007fd653dc8b48
741
+ # @definition={:index=>"ocrs", :type=>"ocr", :body=>{:query=>{:query_string=>"Moomintroll"},
742
+ # :highlight=>{:fields=>{:file=>{}}}}},
743
+ # @klass=[PROXY] OCR,
744
+ # @options={}>>
745
+
746
+ results.count
747
+ => 1
748
+
749
+ results.first.highlight.file.count
750
+ => 2
751
+
752
+ results.first.highlight.file
753
+ => [" <em>his</em> ears in the first snowdrift of <em>his</em> experience. It felt unpleasantly prickly \nto <em>his</em> velvet skin",
754
+ ", 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
+ ```
756
+
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).
758
+
650
759
  ## Contributing
651
760
 
652
761
  Anyone and everyone is welcome to contribute. Go crazy.
@@ -670,4 +779,4 @@ Many thanks to Christopher Knight [@NomadicKnight](https://twitter.com/Nomadic_K
670
779
  ## License
671
780
 
672
781
  Apache License Version 2.0
673
- http://apache.org/licenses/LICENSE-2.0.txt
782
+ http://apache.org/licenses/LICENSE-2.0.txt
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.platform = Gem::Platform::RUBY
10
10
  spec.authors = "MJ Suhonos"
11
11
  spec.email = "mj@suhonos.ca"
12
- spec.summary = %q{Opinionated ActiveModel framework.}
13
- spec.description = %q{Ladder is a metadata framework for RDF modelling, persistence, and full-text indexing.}
12
+ spec.summary = %q{ActiveModel Linked Data framework.}
13
+ spec.description = %q{Dynamic framework for Linked Data modelling, persistence, and full-text indexing.}
14
14
  spec.homepage = "https://github.com/ladder/ladder"
15
15
  spec.license = "APACHE2"
16
16
  spec.required_ruby_version = '>= 1.9.3'
@@ -25,10 +25,15 @@ module Ladder::File
25
25
  ##
26
26
  # Make save behave like Mongoid::Document as much as possible
27
27
  def save
28
+ # FIXME: this raises on a freshly-retrieved document; should either set @file or catch @grid_file
28
29
  raise Mongoid::Errors::InvalidValue.new(IO, NilClass) if file.nil?
29
30
 
30
- attributes[:content_type] = file.content_type if file.respond_to? :content_type
31
- @grid_file ? @grid_file.save : !! @grid_file = self.class.grid.put(file, attributes.symbolize_keys)
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
32
37
  end
33
38
 
34
39
  ##
@@ -5,83 +5,14 @@ require 'elasticsearch/model/callbacks'
5
5
  module Ladder::Searchable
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ autoload :Resource, 'ladder/searchable/resource'
9
+ autoload :File, 'ladder/searchable/file'
10
+
8
11
  included do
9
12
  include Elasticsearch::Model
10
13
  include Elasticsearch::Model::Callbacks
11
- end
12
-
13
- ##
14
- # Generate a qname-based JSON representation
15
- #
16
- def as_qname(opts = {})
17
- qname_hash = type.empty? ? {} : {rdf: {type: type.first.pname }}
18
-
19
- resource_class.properties.each do |field_name, property|
20
- ns, name = property.predicate.qname
21
- qname_hash[ns] ||= Hash.new
22
-
23
- object = self.send(field_name)
24
-
25
- if relations.keys.include? field_name
26
- if opts[:related]
27
- qname_hash[ns][name] = object.to_a.map { |obj| obj.as_qname }
28
- else
29
- qname_hash[ns][name] = object.to_a.map { |obj| "#{obj.class.name.underscore.pluralize}:#{obj.id}" }
30
- end
31
- elsif fields.keys.include? field_name
32
- qname_hash[ns][name] = read_attribute(field_name)
33
- end
34
- end
35
-
36
- qname_hash
37
- end
38
-
39
- private
40
-
41
- ##
42
- # Return a framed, compacted JSON-LD representation
43
- # by embedding related objects from the graph
44
- #
45
- # NB: Will NOT embed related objects with same @type. Spec under discussion, see https://github.com/json-ld/json-ld.org/issues/110
46
- def as_framed_jsonld
47
- json_hash = as_jsonld related: true
48
- context = json_hash['@context']
49
- frame = {'@context' => context, '@type' => type.first.pname}
50
- JSON::LD::API.compact(JSON::LD::API.frame(json_hash, frame), context)
51
- end
52
-
53
- ##
54
- # Force autosave of related documents using Mongoid-defined methods
55
- # Required for explicit autosave prior to after_update index callbacks
56
- #
57
- def autosave
58
- methods.select{|i| i[/autosave_documents/] }.each{|m| send m}
59
- end
60
-
61
- module ClassMethods
62
-
63
- ##
64
- # Specify type of serialization to use for indexing
65
- #
66
- def index_for_search(opts = {})
67
- case opts[:as]
68
- when :jsonld
69
- if opts[:related]
70
- define_method(:as_indexed_json) { |opts = {}| autosave; as_framed_jsonld }
71
- else
72
- define_method(:as_indexed_json) { |opts = {}| as_jsonld }
73
- end
74
- when :qname
75
- if opts[:related]
76
- define_method(:as_indexed_json) { |opts = {}| as_qname related: true }
77
- else
78
- define_method(:as_indexed_json) { |opts = {}| as_qname }
79
- end
80
- else
81
- define_method(:as_indexed_json) { |opts = {}| as_json(except: ['id', '_id']) }
82
- end
83
- end
84
14
 
85
- end
86
-
15
+ include Ladder::Searchable::Resource if self.ancestors.include? Ladder::Resource
16
+ include Ladder::Searchable::File if self.ancestors.include? Ladder::File
17
+ end
87
18
  end
@@ -0,0 +1,30 @@
1
+ module Ladder::Searchable::File
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Index binary content using Elasticsearch mapper attachment plugin
6
+ # https://github.com/elasticsearch/elasticsearch-mapper-attachments
7
+ mapping _source: { enabled: false } do
8
+ indexes :file, type: 'attachment', fields: {
9
+ file: { store: true },
10
+ title: { store: true },
11
+ date: { store: true },
12
+ author: { store: true },
13
+ keywords: { store: true },
14
+ content_type: { store: true },
15
+ content_length: { store: true },
16
+ language: { store: true }
17
+ }
18
+ end
19
+
20
+ # Explicitly set mapping definition on index
21
+ self.__elasticsearch__.create_index!
22
+ end
23
+
24
+ ##
25
+ # Return a Base64-encoded copy of data
26
+ def as_indexed_json(opts = {})
27
+ { file: Base64.encode64(data) }
28
+ end
29
+
30
+ end
@@ -0,0 +1,78 @@
1
+ module Ladder::Searchable::Resource
2
+ extend ActiveSupport::Concern
3
+
4
+ ##
5
+ # Generate a qname-based JSON representation
6
+ #
7
+ def as_qname(opts = {})
8
+ qname_hash = type.empty? ? {} : {rdf: {type: type.first.pname }}
9
+
10
+ resource_class.properties.each do |field_name, property|
11
+ ns, name = property.predicate.qname
12
+ qname_hash[ns] ||= Hash.new
13
+
14
+ object = self.send(field_name)
15
+
16
+ if relations.keys.include? field_name
17
+ if opts[:related]
18
+ qname_hash[ns][name] = object.to_a.map { |obj| obj.as_qname }
19
+ else
20
+ qname_hash[ns][name] = object.to_a.map { |obj| "#{obj.class.name.underscore.pluralize}:#{obj.id}" }
21
+ end
22
+ elsif fields.keys.include? field_name
23
+ qname_hash[ns][name] = read_attribute(field_name)
24
+ end
25
+ end
26
+
27
+ qname_hash
28
+ end
29
+
30
+ private
31
+
32
+ ##
33
+ # Return a framed, compacted JSON-LD representation
34
+ # by embedding related objects from the graph
35
+ #
36
+ # NB: Will NOT embed related objects with same @type. Spec under discussion, see https://github.com/json-ld/json-ld.org/issues/110
37
+ def as_framed_jsonld
38
+ json_hash = as_jsonld related: true
39
+ context = json_hash['@context']
40
+ frame = {'@context' => context, '@type' => type.first.pname}
41
+ JSON::LD::API.compact(JSON::LD::API.frame(json_hash, frame), context)
42
+ end
43
+
44
+ ##
45
+ # Force autosave of related documents using Mongoid-defined methods
46
+ # Required for explicit autosave prior to after_update index callbacks
47
+ #
48
+ def autosave
49
+ methods.select{|i| i[/autosave_documents/] }.each{|m| send m}
50
+ end
51
+
52
+ module ClassMethods
53
+
54
+ ##
55
+ # Specify type of serialization to use for indexing
56
+ #
57
+ def index_for_search(opts = {})
58
+ case opts[:as]
59
+ when :jsonld
60
+ if opts[:related]
61
+ define_method(:as_indexed_json) { |opts = {}| autosave; as_framed_jsonld }
62
+ else
63
+ define_method(:as_indexed_json) { |opts = {}| as_jsonld }
64
+ end
65
+ when :qname
66
+ if opts[:related]
67
+ define_method(:as_indexed_json) { |opts = {}| as_qname related: true }
68
+ else
69
+ define_method(:as_indexed_json) { |opts = {}| as_qname }
70
+ end
71
+ else
72
+ define_method(:as_indexed_json) { |opts = {}| as_json(except: [:id, :_id]) }
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -1,3 +1,3 @@
1
1
  module Ladder
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ladder::Searchable::File do
4
+ before do
5
+ Mongoid.load!('mongoid.yml', :development)
6
+ Mongoid.logger.level = Moped.logger.level = Logger::DEBUG
7
+ Mongoid.purge!
8
+
9
+ Elasticsearch::Model.client = Elasticsearch::Client.new host: 'localhost:9200', log: true
10
+ Elasticsearch::Model.client.indices.delete index: '_all'
11
+
12
+ LADDER_BASE_URI = 'http://example.org'
13
+
14
+ class Datastream
15
+ include Ladder::File
16
+ include Ladder::Searchable
17
+ end
18
+ end
19
+
20
+ shared_context 'searchable' do
21
+ before do
22
+ subject.save
23
+ Elasticsearch::Model.client.indices.flush
24
+ end
25
+
26
+ it 'should exist in the index' do
27
+ results = subject.class.search('Moomin*')
28
+ expect(results.count).to eq 1
29
+ expect(results.first.id).to eq subject.id.to_s
30
+ end
31
+
32
+ it 'should contain full-text content' do
33
+ results = subject.class.search 'Moomin*', fields: '*'
34
+ expect(results.count).to eq 1
35
+ expect(results.first.fields.file.first).to include 'Moomin'
36
+ end
37
+ end
38
+
39
+ context 'with data from file' do
40
+ TEST_FILE = './spec/shared/moomin.pdf'
41
+
42
+ let(:subject) { Datastream.new file: open(TEST_FILE) }
43
+ let(:source) { open(TEST_FILE).read } # ASCII-8BIT (binary)
44
+
45
+ it_behaves_like 'a File'
46
+
47
+ include_context 'searchable'
48
+ end
49
+
50
+ context 'with data from string after creation' do
51
+ data = "And so Moomintroll was helplessly thrown out into a strange and dangerous world and dropped up to his ears in the first snowdrift of his experience. It felt unpleasantly prickly to his velvet skin, but at the same time his nose caught a new smell. It was a more serious smell than any he had met before, and slightly frightening. But it made him wide awake and greatly interested."
52
+
53
+ let(:subject) { Datastream.new }
54
+ let(:source) { data } # UTF-8 (string)
55
+
56
+ before do
57
+ subject.file = StringIO.new(source)
58
+ end
59
+
60
+ it_behaves_like 'a File'
61
+
62
+ include_context 'searchable'
63
+ end
64
+
65
+ after do
66
+ Object.send(:remove_const, "Datastream") if Object
67
+ end
68
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Ladder::Searchable do
3
+ describe Ladder::Searchable::Resource do
4
4
  before do
5
5
  Mongoid.load!('mongoid.yml', :development)
6
6
  Mongoid.logger.level = Moped.logger.level = Logger::DEBUG
@@ -38,7 +38,7 @@ shared_examples 'a File' do
38
38
 
39
39
  describe '#data' do
40
40
  it 'should return a data stream' do
41
- expect(subject.data).to eq source
41
+ expect(subject.data).to eq source.force_encoding(subject.data.encoding)
42
42
  end
43
43
  end
44
44
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ladder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - MJ Suhonos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-06 00:00:00.000000000 Z
11
+ date: 2015-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mongoid
@@ -178,7 +178,7 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0.9'
181
- description: Ladder is a metadata framework for RDF modelling, persistence, and full-text
181
+ description: Dynamic framework for Linked Data modelling, persistence, and full-text
182
182
  indexing.
183
183
  email: mj@suhonos.ca
184
184
  executables: []
@@ -199,13 +199,16 @@ files:
199
199
  - lib/ladder/resource.rb
200
200
  - lib/ladder/resource/dynamic.rb
201
201
  - lib/ladder/searchable.rb
202
+ - lib/ladder/searchable/file.rb
203
+ - lib/ladder/searchable/resource.rb
202
204
  - lib/ladder/version.rb
203
205
  - logo.png
204
206
  - mongoid.yml
207
+ - spec/ladder/file/searchable_spec.rb
205
208
  - spec/ladder/file_spec.rb
206
209
  - spec/ladder/resource/dynamic_spec.rb
210
+ - spec/ladder/resource/searchable_spec.rb
207
211
  - spec/ladder/resource_spec.rb
208
- - spec/ladder/searchable_spec.rb
209
212
  - spec/shared/file.rb
210
213
  - spec/shared/moomin.pdf
211
214
  - spec/shared/resource.rb
@@ -233,12 +236,13 @@ rubyforge_project:
233
236
  rubygems_version: 2.2.2
234
237
  signing_key:
235
238
  specification_version: 4
236
- summary: Opinionated ActiveModel framework.
239
+ summary: ActiveModel Linked Data framework.
237
240
  test_files:
241
+ - spec/ladder/file/searchable_spec.rb
238
242
  - spec/ladder/file_spec.rb
239
243
  - spec/ladder/resource/dynamic_spec.rb
244
+ - spec/ladder/resource/searchable_spec.rb
240
245
  - spec/ladder/resource_spec.rb
241
- - spec/ladder/searchable_spec.rb
242
246
  - spec/shared/file.rb
243
247
  - spec/shared/moomin.pdf
244
248
  - spec/shared/resource.rb