europeana-blacklight 0.1.0

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.
@@ -0,0 +1,51 @@
1
+ module Europeana
2
+ module Blacklight
3
+ class Document
4
+ ##
5
+ # Methods for obtaining "more like this" documents
6
+ module MoreLikeThis
7
+ # @param [String] param Name of API parameter to restrict query to
8
+ # @return [String]
9
+ def more_like_this_query(param = nil)
10
+ queries = more_like_this_field_queries(param)
11
+ return nil unless queries.size > 0
12
+ field_queries = queries.join(' OR ')
13
+ "(#{field_queries}) NOT europeana_id:\"#{self.id}\""
14
+ end
15
+
16
+ protected
17
+
18
+ def more_like_this_logic
19
+ [
20
+ { param: 'title', fields: 'title', boost: 0.3 },
21
+ { param: 'who', fields: 'proxies.dcCreator', boost: 0.5 },
22
+ { param: 'DATA_PROVIDER', fields: 'aggregations.edmDataProvider', boost: 0.2 },
23
+ { param: 'what', fields: ['proxies.dcType', 'proxies.dcSubject', 'concepts.about'], boost: 0.8 },
24
+ ]
25
+ end
26
+
27
+ def more_like_this_field_terms(*fields)
28
+ fields.flatten.map do |field|
29
+ fetch(field, []).compact
30
+ end.flatten
31
+ end
32
+
33
+ def more_like_this_field_queries(param = nil)
34
+ logic = more_like_this_logic.select do |component|
35
+ param.nil? || component[:param] == param
36
+ end
37
+ logic.map do |component|
38
+ field_terms = more_like_this_field_terms(component[:fields])
39
+ more_like_this_param_query(component[:param], field_terms, component[:boost])
40
+ end.compact
41
+ end
42
+
43
+ def more_like_this_param_query(param, terms, boost)
44
+ return nil unless terms.present?
45
+ or_terms = terms.map { |v| '"' + v + '"' }.join(' OR ')
46
+ "#{param}: (#{or_terms})^#{boost}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,88 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Europeana
4
+ module Blacklight
5
+ class Document
6
+ ##
7
+ # Methods for working with relations between objects in Europeana documents
8
+ module Relations
9
+ attr_reader :relations
10
+
11
+ def fetch_through_relation(key, *default)
12
+ field = nested_field_key(key)
13
+ container = nested_field_container(key)
14
+ value = [container].flatten.compact.collect do |target|
15
+ target.fetch(field, *default)
16
+ end.compact.flatten
17
+ end
18
+
19
+ def extract_relations(source_doc)
20
+ fields = source_doc.except(relation_keys)
21
+
22
+ relations = HashWithIndifferentAccess.new
23
+
24
+ relation_keys.each do |k|
25
+ if source_doc.key?(k)
26
+ if source_doc[k].is_a?(Hash)
27
+ relations[k] = self.class.new(source_doc[k], nil)
28
+ elsif source_doc[k].is_a?(Array)
29
+ relations[k] = source_doc[k].map { |v| self.class.new(v, nil) }
30
+ else
31
+ fail StandardError,
32
+ 'Relations should be a collection of objects.'
33
+ end
34
+ end
35
+ end
36
+
37
+ [fields, relations]
38
+ end
39
+
40
+ def field_in_relation?(field)
41
+ keys = split_edm_key(field)
42
+ (keys.size > 1) && relations.key?(keys.first)
43
+ end
44
+
45
+ def nested_field_key(field)
46
+ split_edm_key(field).last
47
+ end
48
+
49
+ def nested_field_container(field)
50
+ container = self
51
+ if field_in_relation?(field)
52
+ keys = split_edm_key(field)
53
+ field = keys.last
54
+ keys[0..-2].each do |relation_key|
55
+ container = [container].flatten.collect { |d| d.send(relation_key.to_sym) }
56
+ end
57
+ end
58
+ container
59
+ end
60
+
61
+ def method_missing(m, *args, &b)
62
+ if has_relation?(m)
63
+ relations[m.to_s]
64
+ elsif relation_keys.include?(m)
65
+ []
66
+ else
67
+ super
68
+ end
69
+ end
70
+
71
+ def has_relation?(name)
72
+ relations.key?(name.to_s)
73
+ end
74
+
75
+ protected
76
+
77
+ def split_edm_key(key)
78
+ key.to_s.split('.')
79
+ end
80
+
81
+ def relation_keys
82
+ [:agents, :aggregations, :concepts, :europeanaAggregation, :places,
83
+ :providedCHOs, :proxies, :timespans, :webResources]
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,36 @@
1
+ module Europeana
2
+ module Blacklight
3
+ ##
4
+ # Blacklight document presenter for Europeana documents
5
+ class DocumentPresenter < ::Blacklight::DocumentPresenter
6
+ include ActionView::Helpers::AssetTagHelper
7
+
8
+ def render_document_show_field_value(field, options = {})
9
+ render_nested_field_value(field, :show, options)
10
+ end
11
+
12
+ def render_index_field_value(field, options = {})
13
+ render_nested_field_value(field, :index, options)
14
+ end
15
+
16
+ def render_nested_field_value(field, context, options = {})
17
+ key = @document.nested_field_key(field)
18
+ container = @document.nested_field_container(field)
19
+
20
+ field_config = @configuration.send(:"#{context}_fields")[key]
21
+ value = options[:value] || begin
22
+ [container].flatten.compact.collect do |target|
23
+ presenter = self.class.new(target, @controller, @configuration)
24
+ presenter.get_field_values(key, field_config, options)
25
+ end.compact.flatten
26
+ end
27
+
28
+ render_field_value(value, field_config)
29
+ end
30
+
31
+ def get_field_values(field, field_config, options = {})
32
+ Document.localize_lang_map(super)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ module Europeana
2
+ module Blacklight
3
+ ##
4
+ # Facet paginator for Europeana API
5
+ class FacetPaginator < Blacklight::FacetPaginator
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,91 @@
1
+ module Europeana
2
+ module Blacklight
3
+ ##
4
+ # Europeana API response for BL
5
+ class Response < HashWithIndifferentAccess
6
+ require 'europeana/blacklight/response/pagination'
7
+ require 'europeana/blacklight/response/facets'
8
+ require 'europeana/blacklight/response/more_like_this'
9
+
10
+ include Pagination
11
+ include Facets
12
+ include MoreLikeThis
13
+
14
+ attr_reader :request_params
15
+ attr_accessor :document_model, :blacklight_config
16
+
17
+ def initialize(data, request_params, options = {})
18
+ super(force_to_utf8(data))
19
+ @request_params = request_params
20
+ self.document_model = options[:document_model] || Document
21
+ self.blacklight_config = options[:blacklight_config]
22
+ end
23
+
24
+ def update(other_hash)
25
+ other_hash.each_pair { |key, value| self[key] = value }
26
+ self
27
+ end
28
+
29
+ def params
30
+ self['params'] ? self['params'] : request_params
31
+ end
32
+
33
+ def rows
34
+ params[:rows].to_i
35
+ end
36
+
37
+ def sort
38
+ params[:sort]
39
+ end
40
+
41
+ def documents
42
+ @documents ||= (self.key?('object') ? [self['object']] : (self['items'] || [])).collect do |doc|
43
+ document_model.new(doc, self)
44
+ end
45
+ end
46
+ alias_method :docs, :documents
47
+
48
+ def grouped
49
+ []
50
+ end
51
+
52
+ def group(_key)
53
+ nil
54
+ end
55
+
56
+ def grouped?
57
+ false
58
+ end
59
+
60
+ def export_formats
61
+ documents.map { |x| x.export_formats.keys }.flatten.uniq
62
+ end
63
+
64
+ def total
65
+ self[:totalResults].to_s.to_i
66
+ end
67
+
68
+ def start
69
+ params[:start].to_s.to_i - 1
70
+ end
71
+
72
+ def empty?
73
+ total == 0
74
+ end
75
+
76
+ private
77
+
78
+ def force_to_utf8(value)
79
+ case value
80
+ when Hash
81
+ value.each { |k, v| value[k] = force_to_utf8(v) }
82
+ when Array
83
+ value.each { |v| force_to_utf8(v) }
84
+ when String
85
+ value.force_encoding('utf-8') if value.respond_to?(:force_encoding)
86
+ end
87
+ value
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,145 @@
1
+ module Europeana
2
+ module Blacklight
3
+ class Response
4
+ ##
5
+ # Facets for {Europeana::Blacklight::Response}
6
+ #
7
+ # Based on {Blacklight::SolrResponse::Facets} v5.10.2
8
+ module Facets
9
+ require 'ostruct'
10
+
11
+ # represents a facet value; which is a field value and its hit count
12
+ class FacetItem < OpenStruct
13
+ def initialize(*args)
14
+ options = args.extract_options!
15
+
16
+ # Backwards-compat method signature
17
+ value = args.shift
18
+ hits = args.shift
19
+
20
+ options[:value] = value if value
21
+ options[:hits] = hits if hits
22
+
23
+ super(options)
24
+ end
25
+
26
+ def label
27
+ super || value
28
+ end
29
+
30
+ def as_json(props = nil)
31
+ table.as_json(props)
32
+ end
33
+ end
34
+
35
+ # represents a facet; which is a field and its values
36
+ class FacetField
37
+ attr_reader :name, :items
38
+
39
+ def initialize(name, items, options = {})
40
+ @name, @items = name, items
41
+ @options = options
42
+ end
43
+
44
+ def limit
45
+ @options[:limit] || default_limit
46
+ end
47
+
48
+ def offset
49
+ @options[:offset] || default_offset
50
+ end
51
+
52
+ def sort
53
+ # Europeana API does not support facet sorting
54
+ nil
55
+ end
56
+
57
+ private
58
+
59
+ # @see http://labs.europeana.eu/api/search/#offset-and-limit-of-facets
60
+ def default_limit
61
+ 100
62
+ end
63
+
64
+ # @see http://labs.europeana.eu/api/search/#offset-and-limit-of-facets
65
+ def default_offset
66
+ 0
67
+ end
68
+ end
69
+
70
+ def aggregations
71
+ @aggregations ||= {}.merge(facet_field_aggregations).merge(facet_query_aggregations)
72
+ end
73
+
74
+ def facet_fields
75
+ @facet_fields ||= self['facets'] || []
76
+ end
77
+
78
+ def facet_queries
79
+ @facet_queries ||= self['facet_queries'] || {}
80
+ end
81
+
82
+ private
83
+
84
+ ##
85
+ # Convert API's facets response into a hash of
86
+ # {Europeana::Blacklight::Response::Facet::FacetField} objects
87
+ def facet_field_aggregations
88
+ facet_fields.each_with_object({}) do |facet, hash|
89
+ facet_field_name = facet['name']
90
+
91
+ items = facet['fields'].collect do |value|
92
+ FacetItem.new(value: value['label'], hits: value['count'])
93
+ end
94
+
95
+ hash[facet_field_name] = FacetField.new(facet_field_name, items, facet_field_aggregation_options(facet_field_name))
96
+
97
+ if blacklight_config && !blacklight_config.facet_fields[facet_field_name]
98
+ # alias all the possible blacklight config names..
99
+ blacklight_config.facet_fields.select { |_k, v| v.field == facet_field_name }.each do |key, _|
100
+ hash[key] = hash[facet_field_name]
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def facet_field_aggregation_options(name)
107
+ options = {}
108
+
109
+ if params[:"f.#{name}.facet.limit"]
110
+ options[:limit] = params[:"f.#{name}.facet.limit"].to_i
111
+ elsif params[:'facet.limit']
112
+ options[:limit] = params[:'facet.limit'].to_i
113
+ end
114
+
115
+ if params[:"f.#{name}.facet.offset"]
116
+ options[:offset] = params[:"f.#{name}.facet.offset"].to_i
117
+ elsif params[:'facet.offset']
118
+ options[:offset] = params[:'facet.offset'].to_i
119
+ end
120
+
121
+ options
122
+ end
123
+
124
+ ##
125
+ # Aggregate API's facet_query response into the virtual facet fields
126
+ # defined in the blacklight configuration
127
+ def facet_query_aggregations
128
+ return {} unless blacklight_config
129
+
130
+ blacklight_config.facet_fields.select { |_k, v| v.query }.each_with_object({}) do |(field_name, facet_field), hash|
131
+ salient_facet_queries = facet_field.query.map { |_k, x| x[:fq] }
132
+ items = []
133
+ facet_queries.select { |k, _v| salient_facet_queries.include?(k) }.reject { |_value, hits| hits == 0 }.map do |value, hits|
134
+ salient_fields = facet_field.query.select { |_k, v| v[:fq] == value }
135
+ key = ((salient_fields.keys if salient_fields.respond_to? :keys) || salient_fields.first).first
136
+ items << FacetItem.new(value: key, hits: hits, label: facet_field.query[key][:label])
137
+ end
138
+
139
+ hash[field_name] = FacetField.new(field_name, items)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,19 @@
1
+ module Europeana
2
+ module Blacklight
3
+ class Response
4
+ ##
5
+ # MLT for{Europeana::Blacklight::Response}
6
+ #
7
+ # This will just return blank objects from the MLT methods BL expects.
8
+ module MoreLikeThis
9
+ def more_like(_document)
10
+ []
11
+ end
12
+
13
+ def more_like_this
14
+ {}
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end