scooby_snacks 0.3

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +54 -0
  6. data/Rakefile +2 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/examples/config/metadata.yml +283 -0
  10. data/examples/config/metadata/shared.yml +0 -0
  11. data/examples/example_config_1/README.md +7 -0
  12. data/examples/example_config_1/metadata.yml +96 -0
  13. data/examples/example_config_1/shared_schema.yml +93 -0
  14. data/examples/example_config_2/README.md +10 -0
  15. data/examples/example_config_2/metadata.yml +20 -0
  16. data/examples/example_config_2/metadata/local/classes.yml +25 -0
  17. data/examples/example_config_2/metadata/local/namespaces.yml +10 -0
  18. data/examples/example_config_2/metadata/local/properties.yml +61 -0
  19. data/examples/example_config_2/metadata/local/work_types.yml +25 -0
  20. data/examples/example_config_2/metadata/shared/geospatial_properties.yml +17 -0
  21. data/examples/example_config_2/metadata/shared/uc/classes.yml +32 -0
  22. data/examples/example_config_2/metadata/shared/uc/properties.yml +133 -0
  23. data/lib/generators/scooby_snacks/install/templates/metadata.yml +251 -0
  24. data/lib/scooby_snacks.rb +11 -0
  25. data/lib/scooby_snacks/blacklight_configuration.rb +68 -0
  26. data/lib/scooby_snacks/field.rb +174 -0
  27. data/lib/scooby_snacks/initialize.rb +4 -0
  28. data/lib/scooby_snacks/metadata_schema.rb +170 -0
  29. data/lib/scooby_snacks/presenter_behavior.rb +10 -0
  30. data/lib/scooby_snacks/solr_behavior.rb +71 -0
  31. data/lib/scooby_snacks/version.rb +3 -0
  32. data/lib/scooby_snacks/work_form_behavior.rb +36 -0
  33. data/lib/scooby_snacks/work_model_behavior.rb +35 -0
  34. data/scooby_snacks.gemspec +35 -0
  35. metadata +108 -0
@@ -0,0 +1,11 @@
1
+ require "scooby_snacks/version"
2
+
3
+ module ScoobySnacks
4
+ require 'scooby_snacks/metadata_schema'
5
+ require 'scooby_snacks/field'
6
+ require 'scooby_snacks/presenter_behavior'
7
+ require 'scooby_snacks/solr_behavior'
8
+ require 'scooby_snacks/work_form_behavior'
9
+ require 'scooby_snacks/work_model_behavior'
10
+ require 'scooby_snacks/blacklight_configuration'
11
+ end
@@ -0,0 +1,68 @@
1
+ module ScoobySnacks
2
+ class BlacklightConfiguration
3
+
4
+ def self.add_all_fields(config)
5
+ self.add_show_fields(config)
6
+ self.add_search_fields(config)
7
+ self.add_facet_fields(config)
8
+ self.add_sort_fields(config)
9
+ self.add_search_result_display_fields(config)
10
+ end
11
+
12
+ def self.add_show_fields(config)
13
+ self.schema.display_fields.each do |field|
14
+ begin
15
+ config.add_show_field field.solr_search_name, label: field.label
16
+ rescue
17
+ Rails.logger.error "error adding field: #{field.solr_search_name} for property #{field.label}. Redundant definition?"
18
+ end
19
+ end
20
+ end
21
+
22
+ def self.add_search_fields(config)
23
+ self.schema.searchable_fields.each do |field|
24
+ config.add_search_field(field.name) do |new_field|
25
+ new_field.label = field.label
26
+ new_field.solr_local_parameters = {
27
+ qf: field.solr_search_name,
28
+ pf: field.solr_search_name
29
+ }
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.add_facet_fields(config)
35
+ self.schema.facet_fields.each do |field|
36
+ config.add_facet_field field.solr_facet_name, {label: field.label, limit: field.facet_limit}
37
+ end
38
+ end
39
+
40
+ def self.add_sort_fields(config)
41
+ self.schema.sortable_fields.each do |field|
42
+ config.add_sort_field field.solr_sort_name, label: field.label
43
+ end
44
+ end
45
+
46
+ def self.add_search_result_display_fields(config)
47
+ self.schema.search_result_display_fields.each do |field|
48
+ config.add_index_field(field.solr_search_name, self.get_index_options(field))
49
+ end
50
+ end
51
+
52
+ def self.get_index_options field
53
+ options = {}
54
+ options[:label] = field.label || field.name
55
+ options[:index_itemprop] = field.itemprop if field.itemprop
56
+ options[:helper_method] = field.helper_method if field.helper_method
57
+ options[:link_to_search] = field.solr_search_name if field.search?
58
+ return options
59
+ end
60
+
61
+ private
62
+
63
+ def self.schema
64
+ ScoobySnacks::METADATA_SCHEMA
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,174 @@
1
+ module ScoobySnacks
2
+ class Field
3
+
4
+ attr_reader :name, :label, :oai_element, :oai_ns
5
+
6
+ def solr_sort_name
7
+ return false unless sort?
8
+ @solr_sort_name ||= Solrizer.solr_name(name, :stored_sortable)
9
+ end
10
+
11
+ def initialize name, raw_array
12
+ @raw_array = raw_array
13
+ @name = name
14
+ @label = raw_array['label'] || name.underscore.humanize
15
+
16
+ if @raw_array['OAI'] && (oai_split = @raw_array['OAI'].split(':',2))
17
+ @oai_ns = oai_split.first.downcase
18
+ @oai_element = oai_split.last
19
+ end
20
+ end
21
+
22
+ # here we define methods for simple boolean attributes associate with metadata fields
23
+ # ("meta-metadata properties")
24
+ ScoobySnacks::MetadataSchema.boolean_attributes.each do |attribute_name|
25
+ # Skip any attribute we have a custom method for
26
+ next if [:controlled].include? attribute_name
27
+ #define a method for this attribute
28
+ define_method("#{attribute_name}?".to_sym) do
29
+ # For boolean attributes, we cache the result to avoid repeated string comparison operations
30
+ attribute = instance_variable_get("@#{attribute_name}")
31
+ return attribute unless attribute.nil?
32
+ attribute = @raw_array[attribute_name.to_s].to_s.downcase.strip == "true"
33
+ instance_variable_set("@#{attribute_name}", attribute)
34
+ return attribute
35
+ end
36
+ end
37
+
38
+ # here we define methods for simple string attributes associate with metadata fields
39
+ # ("meta-metadata properties")
40
+ ScoobySnacks::MetadataSchema.string_attributes.each do |attribute_name|
41
+ # For string attributes, we just pull the result straight from the raw array
42
+ define_method("#{attribute_name}".to_sym) do
43
+ @raw_array[attribute_name.to_s]
44
+ end
45
+ end
46
+
47
+ def controlled?
48
+ return @controlled unless @controlled.nil?
49
+ @controlled = false
50
+ @controlled = true if @raw_array['controlled'].to_s == "true"
51
+ @controlled = true if @raw_array['input'].to_s.include? "controlled"
52
+ @controlled = true if (@raw_array['vocabularies'].is_a?(Array) && !@raw_array['vocabularies'].empty?)
53
+ @controlled = true if (@raw_array['vocabulary'].is_a?(Hash) && !@raw_array['vocabulary'].empty?)
54
+ return @controlled
55
+ end
56
+
57
+ def predicate
58
+ return @predicate if @predicate
59
+ raise ArgumentError.new("invalid predicate definition. Raw array: #{@raw_array.inspect}") if @raw_array["predicate"].nil?
60
+ namespace_prefix = @raw_array["predicate"].split(":").first
61
+ predicate_name = @raw_array["predicate"].split(":",2).last
62
+ #sort out the predicate namespace if necessary
63
+ namespaces = schema.namespaces
64
+ if namespaces.key?(namespace_prefix)
65
+ namespace_url = namespaces[namespace_prefix]
66
+ raise ArgumentError.new("invalid predicate definition: #{@raw_array['predicate']}") unless namespace_url.include?("http")
67
+ @predicate = ::RDF::URI.new(namespace_url + predicate_name)
68
+ elsif defined?("::RDF::Vocab::#{namespace_prefix}".constantize)
69
+ @predicate = "::RDF::Vocab::#{namespace_prefix}".constantize.send predicate_name
70
+ else
71
+ raise ArgumentError.new("invalid predicate definition: #{@raw_array['predicate']}")
72
+ end
73
+ @predicate
74
+ end
75
+
76
+ def date?
77
+ @date ||= (@raw_array['input'].to_s.downcase.include? "date") || (@raw_array['data_type'].to_s.downcase.include? "date")
78
+ end
79
+
80
+ def itemprop
81
+ @raw_array['index_itemprop'] || @raw_array['itemprop']
82
+ end
83
+
84
+ def helper_method
85
+ method_name = (@raw_array['index_helper_method'] || @raw_array['helper_method'])
86
+ method_name.to_sym unless method_name.nil?
87
+ end
88
+
89
+ def search?
90
+ searchable?
91
+ end
92
+
93
+ def sort?
94
+ sortable?
95
+ end
96
+
97
+ def index_itemprop
98
+ itemprop
99
+ end
100
+
101
+ def oai?
102
+ !@oai_element.nil? && !@oai_ns.nil?
103
+ end
104
+
105
+ def display_options
106
+ options = {label: label}
107
+ if search?
108
+ options[:render_as] = :linked
109
+ options[:search_field] = solr_search_name
110
+ end
111
+ return options
112
+ end
113
+
114
+ def in_display_group? group_name
115
+ display_groups.each { |display_group| break true if (display_group.downcase == group_name.to_s.downcase) }==true
116
+ end
117
+
118
+ def search_result_display?
119
+ in_display_group? "search_result"
120
+ end
121
+
122
+ def display_groups
123
+ @raw_array['display_groups'] || Array(@raw_array['display_group'])
124
+ end
125
+
126
+ def display_group
127
+ display_groups.first
128
+ end
129
+
130
+ def vocabularies
131
+ @raw_array['vocabularies'] || Array(@raw_array['vocabulary'])
132
+ end
133
+
134
+ def primary_vocabulary
135
+ vocabularies.first
136
+ end
137
+
138
+ def solr_name(facet: nil, keyword: false, string: false, symbol: false, tokenize: true)
139
+ facet = facet? if facet.nil?
140
+ facet = facet or keyword or string or symbol or !tokenize
141
+ case @raw_array['data_type'].downcase
142
+ when /string/, /keyword/, /symbol/
143
+ type = "s"
144
+ when /date/
145
+ type = "d"
146
+ else
147
+ type = "te"
148
+ end
149
+ type = "s" if facet
150
+ index = (search? or facet) ? "i" : ""
151
+ multiple = multiple? ? "m" : ""
152
+ return "#{name}_#{type}s#{index}#{multiple}"
153
+ end
154
+
155
+ def solr_search_name
156
+ solr_name
157
+ end
158
+
159
+ def solr_facet_name
160
+ solr_name(facet: true)
161
+ end
162
+
163
+ private
164
+
165
+ def schema
166
+ if defined? ScoobySnacks::METADATA_SCHEMA
167
+ return ScoobySnacks::METADATA_SCHEMA
168
+ else
169
+ @schema ||= ScoobySnacks::MetadataSchema.new
170
+ end
171
+ end
172
+
173
+ end
174
+ end
@@ -0,0 +1,4 @@
1
+
2
+ # set the global constant
3
+ ScoobySnacks::METADATA_SCHEMA = ScoobySnacks::MetadataSchema.new
4
+
@@ -0,0 +1,170 @@
1
+ module ScoobySnacks
2
+ class MetadataSchema
3
+
4
+ attr_reader :fields, :namespaces
5
+
6
+ SS_BOOLEAN_ATTRIBUTES = [:facet,
7
+ :searchable,
8
+ :sortable,
9
+ :multiple,
10
+ :full_text_searchable,
11
+ :required,
12
+ :work_title,
13
+ :hidden,
14
+ :stored_in_solr,
15
+ :controlled]
16
+ SS_STRING_ATTRIBUTES = [:facet_limit,
17
+ :helper_method,
18
+ :input,
19
+ :definition]
20
+
21
+ SS_DISPLAY_GROUPS = [:primary,
22
+ :secondary,
23
+ :search_result,
24
+ :editor_primary]
25
+
26
+ # override this locally to define app-specific attributes
27
+ def self.custom_boolean_attributes
28
+ []
29
+ end
30
+
31
+ # override this locally to define app-specific attributes
32
+ def self.custom_string_attributes
33
+ []
34
+ end
35
+
36
+ # override this locally to define app-specific display groups
37
+ def self.custom_display_groups
38
+ []
39
+ end
40
+
41
+ def self.boolean_attributes
42
+ SS_BOOLEAN_ATTRIBUTES + custom_boolean_attributes
43
+ end
44
+
45
+ def self.string_attributes
46
+ SS_STRING_ATTRIBUTES + custom_string_attributes
47
+ end
48
+
49
+ def self.display_groups
50
+ SS_DISPLAY_GROUPS + custom_display_groups
51
+ end
52
+
53
+ def initialize (schema_config_path: nil, raw_schema: nil)
54
+ schema_config_path ||= default_schema_config_path
55
+ raw_schema ||= YAML.load_file(schema_config_path)
56
+ @namespaces = raw_schema['namespaces']
57
+ raw_fields = (raw_schema['fields'] || raw_schema['properties'])
58
+ @fields = raw_fields.except('default').keys.reduce({}) do |fields, field_name|
59
+ field = raw_fields['default'].deep_merge raw_fields[field_name]
60
+ fields[field_name] = ScoobySnacks::Field.new(field_name,field)
61
+ fields
62
+ end
63
+ end
64
+
65
+ # define methods to list display group contents
66
+ display_groups.each do |display_group|
67
+ define_method("#{display_group}_display_field_names") do
68
+ field_names = instance_variable_get("@#{display_group}_display_field_names")
69
+ if field_names.nil?
70
+ send("#{display_group}_display_fields".to_sym).map{|field| field.name}
71
+ else
72
+ field_names
73
+ end
74
+ end
75
+ define_method("#{display_group}_display_fields") do
76
+ field_names = instance_variable_get("@#{display_group}_display_field_names")
77
+ return field_names.map{|name| get_field(name)} unless field_names.nil?
78
+ fields = @fields.values.select{|field| field.in_display_group?(display_group)}
79
+ instance_variable_set("@#{display_group}_display_field_names", fields.map{|field| field.name})
80
+ return fields
81
+ end
82
+ end
83
+
84
+ # Define methods to cache and return lists of fields & field names
85
+ # that share certain boolean characteristics (controlled, required, etc).
86
+ boolean_attributes.each do |attribute|
87
+ # Skip any attribute we have a custom method for
88
+ next if [:work_title].include? attribute
89
+ define_method("#{attribute}_fields".to_sym) do
90
+ @fields.values.select{|field| field.send("#{attribute}?".to_sym)}
91
+ end
92
+ define_method("#{attribute}_field_names".to_sym) do
93
+ field_names = send("#{attribute}_fields".to_sym).map{|field| field.name}
94
+ instance_variable_set("@#{attribute}_field_names".to_sym, field_names)
95
+ end
96
+ end
97
+
98
+ def get_field(name)
99
+ @fields[name.to_s] || @fields[label_map[name.to_s]]
100
+ end
101
+
102
+ def get_property(name)
103
+ get_field(name)
104
+ end
105
+
106
+ def all_field_names
107
+ @fields.keys
108
+ end
109
+
110
+ def default_text_search_solrized_field_names
111
+ # Include all fields marked for full text search that are also individual search fields
112
+ # and therefore excluded from the 'all_text_timv' search field
113
+ field_names = (full_text_searchable_field_names & searchable_field_names).uniq
114
+ field_solr_names = field_names.map{|field_name| get_field(field_name).solr_search_name }
115
+ return( field_solr_names + [full_text_field_name] )
116
+ end
117
+
118
+ def full_text_field_name
119
+ "all_text_timv"
120
+ end
121
+
122
+ def work_title_field
123
+ @fields.values.select{|field| field.in_display_group?("title") || field.work_title?}.first
124
+ end
125
+
126
+ def work_title_field_name
127
+ work_title_field.name
128
+ end
129
+
130
+ def display_field_names
131
+ primary_display_field_names + secondary_display_field_names
132
+ end
133
+
134
+ def display_fields
135
+ primary_display_fields + secondary_display_fields
136
+ end
137
+
138
+ # A few aliases for these methods since I've been using both conventions
139
+ def sort_fields
140
+ sortable_fields
141
+ end
142
+
143
+ def sort_field_names
144
+ sortable_field_names
145
+ end
146
+
147
+ def search_fields
148
+ searchable_fields
149
+ end
150
+
151
+ def search_field_names
152
+ searchable_field_names
153
+ end
154
+
155
+ private
156
+
157
+ def label_map
158
+ @label_map ||= @fields.values.reduce({}){|map,field| map[field.label] = field.name if field.label.present?; map }
159
+ end
160
+
161
+ def default_schema_config_path
162
+ @schema_config_path ||= File.join(Rails.root.to_s,'config',schema_config_filename)
163
+ end
164
+
165
+ def schema_config_filename
166
+ "metadata.yml"
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,10 @@
1
+ module ScoobySnacks::PresenterBehavior
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ ScoobySnacks::METADATA_SCHEMA.display_fields.each do |field|
5
+ next if respond_to? field.name
6
+ delegate field.name.to_sym, to: :solr_document
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,71 @@
1
+ module ScoobySnacks::SolrBehavior
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+
6
+ def attribute(name, type, field)
7
+ define_method name do
8
+ type.coerce(self[field])
9
+ end
10
+ end
11
+
12
+ def solr_name(*args)
13
+ if ScoobySnacks::METADATA_SCHEMA.all_field_names.include?(args.first)
14
+ ScoobySnacks::METADATA_SCHEMA.get_field(args.first).solr_name
15
+ else
16
+ Solrizer.solr_name(*args)
17
+ end
18
+ end
19
+
20
+ def add_field_semantics(label,solr_name)
21
+ field_semantics.merge!(label => Array.wrap(solr_name)) {|key, old_val, new_val| Array.wrap(old_val) + Array.wrap(new_val)}
22
+ end
23
+
24
+ end
25
+
26
+
27
+ #TODO This has to do with solr coercing the right type
28
+ # for each field. I need to remember exactly how this
29
+ # works (this part of the code is old & borrowed)
30
+ module Solr
31
+ class Array
32
+ # @return [Array]
33
+ def self.coerce(input)
34
+ ::Array.wrap(input)
35
+ end
36
+ end
37
+
38
+ class String
39
+ # @return [String]
40
+ def self.coerce(input)
41
+ ::Array.wrap(input).first
42
+ end
43
+ end
44
+
45
+ class Date
46
+ # @return [Date]
47
+ def self.coerce(input)
48
+ field = String.coerce(input)
49
+ return if field.blank?
50
+ begin
51
+ ::Date.parse(field)
52
+ rescue ArgumentError
53
+ Rails.logger.info "Unable to parse date: #{field.first.inspect}"
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+ included do
61
+
62
+ # Loop through all properties from all work types
63
+ ScoobySnacks::METADATA_SCHEMA.stored_in_solr_fields.each do |field|
64
+ next if respond_to? field.name
65
+ # define a attribute for the current property
66
+ attribute(field.name.to_sym, (field.date? ? Solr::Date : Solr::Array), field.solr_search_name) unless field.hidden?
67
+ add_field_semantics(field.oai_element, field.solr_search_name) if (field.oai? && field.oai_ns == 'dc')
68
+ end
69
+ end
70
+ end
71
+