module Exlibris module Primo # == Overview # Exlibris::Primo::Holding represents a Primo availibrary entry. # An instance of Exlibris::Primo::Holding can be created by passing # in a set of parameters containing the data for the holding. # Valid parameters include: # :record_id, :title, :author, :source_id, :original_source_id, :source_record_id, # :availlibrary,:institution_code, :library_code, :status_code, :id_one, :id_two, # :origin, :display_type, :coverage, :notes, :url, :request_url, :source_data # When creating an instance of Exlibris::Primo::Holding, calling # classes may send in a :config hash that contains config mappings # for decoding libraries and statuses. The :config hash should be # of the form # {"libraries" => {"library_code1" => "library_display_1", "library_code2" => "library_display_1"}, "statuses" => {"status_code1" => "status_display_1", "status_code2" => "status_display_2"}} # The config can also include information about Primo::Source classes in the form: # "sources" => {"source_id1" => {"class_name" => "SourceKlassName", "source_config1" => "source_config_one"}} # Primo::Source classes can be used to represent a Primo source for expanding holdings # information, linking to Primo sources, and storing additional metadata based on those sources. # In order to create a source class, implementations should extend Exlibris::Primo::Holding. # # == Tips on Extending # When extending the class, a few basics guidelines should be observed. # 1. A Exlibris::Primo::Holding is initialized from random Hash of parameters. # Instance variables are created from these parameters for use in the class. # # 2. A Exlibris::Primo::Holding can also be initialized from an input # Exlibris::Primo::Holding by specifying the reserved # parameter name :holding, i.e. :holding => input_holding. # If the input holding has instance variables that are also specified in # the random Hash, the value in the Hash takes precedence. # # 3. The following methods are available for overriding: # expand - expand holdings information based on data source. default: [self] # dedup? - does this data source contain duplicate holdings that need to be deduped? default: false # # 4. Additional source data should be saved in the @source_data instance variable. # @source_data is a hash that can contain any number of string elements, # perfect for storing local source information. # # == Examples # Example of Primo source implementations are: # * Exlibris::Primo::Source::Aleph class Holding @base_attributes = [ :record_id, :title, :author, :source_id, :original_source_id, :source_record_id, :availlibrary, :institution_code, :institution, :library_code, :library, :status_code, :status, :id_one, :id_two, :origin, :display_type, :coverage, :notes, :url, :request_url, :source_data ] # Make sure attribute you're aliasing in in base_attributes @attribute_aliases = { :collection => :id_one, :call_number => :id_two } @required_parameters = [ :base_url, :record_id, :source_id, :original_source_id, :source_record_id, :availlibrary, :institution_code, :library_code, :id_one, :id_two, :status_code ] @parameter_default_values = { :vid => "DEFAULT", :config => {}, :max_holdings => 10, :coverage => [], :source_data => {} } @decode_variables = { :institution => {}, :library => { :address => "libraries" }, :status => { :address => "statuses" } } class << self; attr_reader :base_attributes, :attribute_aliases, :required_parameters, :parameter_default_values, :decode_variables end def initialize(parameters={}) # Set attr_readers base_attributes = (self.class.base_attributes.nil?) ? Exlibris::Primo::Holding.base_attributes : self.class.base_attributes base_attributes.each { |attribute| self.class.send(:attr_reader, attribute) } # Defensive copy the holding parameter. holding = parameters[:holding].clone unless parameters[:holding].nil? raise "Initialization error in #{self.class}. Unexpected holding parameter: #{holding.class}." unless holding.kind_of? Holding or holding.nil? # Copy the defensive copy of holding to self. holding.instance_variables.each { |name| instance_variable_set((name).to_sym, holding.instance_variable_get(name)) } if holding.kind_of? Holding # Add required instance variables, raising an exception if they're missing # Params passed in overwrite instance variables copied from the holding required_parameters = (self.class.required_parameters.nil?) ? Exlibris::Primo::Holding.required_parameters : self.class.required_parameters required_parameters.each do |param| instance_variable_set( "@#{param}".to_sym, parameters.delete(param) { instance_variable_get("@#{param}") if instance_variable_defined?("@#{param}") } ) raise_required_parameter_error param unless instance_variable_defined?("@#{param}") end # Set additional instance variables from passed parameters # Params passed in overwrite instance variables copied from the holding parameters.each { |param, value| instance_variable_set("@#{param}".to_sym, value) } # If appropriate, add defaults to non-required elements parameter_default_values = (self.class.parameter_default_values.nil?) ? Exlibris::Primo::Holding.parameter_default_values : self.class.parameter_default_values parameter_default_values.each { |param, default| instance_variable_set("@#{param}".to_sym, default) unless instance_variable_defined?("@#{param}") } # Set decoded fields decode_variables = (self.class.decode_variables.nil?) ? Exlibris::Primo::Holding.decode_variables : self.class.decode_variables decode_variables.each { |var, decode_params| decode var, decode_params, true } # Deep link URL to record @url = primo_url if @url.nil? # Set source parameters @source_config = @config["sources"][source_id] unless @config["sources"].nil? @source_class = @source_config["class_name"] unless @source_config.nil? @source_url = @source_config["base_url"] unless @source_config.nil? @source_type = @source_config["type"] unless @source_config.nil? @source_data = { :source_class => @source_class, :source_url => @source_url, :source_type => @source_type } # Set aliases for convenience attribute_aliases = (self.class.attribute_aliases.nil?) ? Exlibris::Primo::Holding.attribute_aliases : self.class.attribute_aliases attribute_aliases.each { |alias_name, method_name| begin self.class.send(:alias_method, alias_name.to_sym, method_name.to_sym) rescue NameError => ne raise NameError, "Error in #{self}. Make sure method, #{method_name}, is defined. You may need to add it to #{self} @base_attributes.\nRoot exception: #{ne.message}" end } end # Returns an array of self. # Should be overridden by source subclasses to map multiple holdings # to one availlibrary. def expand return [self] end # Determine if we're de-duplicating. # Should be overridden by source subclasses if appropriate. def dedup? return false end # Return this holding as a new holdings subclass instance based on source def to_source return self if @source_class.nil? begin # Get source class in Primo::Source module return Exlibris::Primo::Source.const_get(@source_class).new(:holding => self) rescue Exception => e Rails.logger.error("#{e.message}") Rails.logger.error("Class #{@source_class} can't be found in Exlibris::Primo::Source. Please check primo.yml to ensure the class_name is defined correctly. Not converting to source.") return self end end # Convenience method for making base attributes accessible via Hash-like syntax. def [](key) raise "Error in #{self.class}. #{key} doesn't exist or is restricted." unless self.class.base_attributes.include?(key) method(key).call end protected # Decode based on the pased in config def decode(var, decode_params={}, refresh=false) return instance_variable_get("@#{var}") unless (not instance_variable_defined?("@#{var}")) or refresh code_sym = (decode_params[:code].nil?) ? "#{var}_code".to_sym : decode_params[:code] code = instance_variable_get("@#{code_sym}") config_sym = (decode_params[:config].nil?) ? :config : decode_params[:config] config = instance_variable_get("@#{config_sym}") address = (decode_params[:address].nil?) ? "#{var}s" : decode_params[:address] instance_variable_set("@#{var}", (config[address].nil? or config[address][code].nil?) ? code : config[address][code]) unless code.nil? end # Returns Primo deep link URL to record def primo_url "#{@base_url}/primo_library/libweb/action/dlDisplay.do?docId=#{@record_id}&institution=#{@institution_code}&vid=#{@vid}" end private def raise_required_parameter_error(parameter) raise "Initialization error in #{self.class}. Missing required parameter: #{parameter}." end end end end