class Manifestation < ActiveRecord::Base enju_manifestation_viewer if defined?(EnjuManifestationViewer) enju_ndl_ndl_search if defined?(EnjuNdl) enju_loc_search if defined?(EnjuLoc) enju_nii_cinii_books if defined?(EnjuNii) enju_bookmark_manifestation_model if defined?(EnjuBookmark) has_many :creates, dependent: :destroy, foreign_key: 'work_id' has_many :creators, through: :creates, source: :agent #, order: 'creates.position' has_many :realizes, dependent: :destroy, foreign_key: 'expression_id' has_many :contributors, through: :realizes, source: :agent #, order: 'realizes.position' has_many :produces, dependent: :destroy, foreign_key: 'manifestation_id' has_many :publishers, through: :produces, source: :agent #, order: 'produces.position' has_many :items, dependent: :destroy has_many :children, foreign_key: 'parent_id', class_name: 'ManifestationRelationship', dependent: :destroy has_many :parents, foreign_key: 'child_id', class_name: 'ManifestationRelationship', dependent: :destroy has_many :derived_manifestations, through: :children, source: :child has_many :original_manifestations, through: :parents, source: :parent has_many :picture_files, as: :picture_attachable, dependent: :destroy belongs_to :language belongs_to :carrier_type belongs_to :manifestation_content_type, class_name: 'ContentType', foreign_key: 'content_type_id' has_many :series_statements belongs_to :frequency belongs_to :required_role, class_name: 'Role', foreign_key: 'required_role_id', validate: true has_one :resource_import_result has_many :identifiers, dependent: :destroy accepts_nested_attributes_for :creators, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :contributors, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :publishers, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :series_statements, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :identifiers, allow_destroy: true, reject_if: :all_blank searchable do text :title, default_boost: 2 do titles end [ :fulltext, :note, :creator, :contributor, :publisher, :description, :statement_of_responsibility ].each do |field| text field do if series_master? derived_manifestations.map{|c| c.send(field) }.flatten.compact else self.send(field) end end end text :item_identifier do if series_master? root_series_statement.root_manifestation.items.pluck(:item_identifier, :binding_item_identifier).flatten.compact else items.pluck(:item_identifier, :binding_item_identifier).flatten.compact end end string :call_number, multiple: true do items.pluck(:call_number) end string :title, multiple: true # text フィールドだと区切りのない文字列の index が上手く作成 #できなかったので。 downcase することにした。 #他の string 項目も同様の問題があるので、必要な項目は同様の処置が必要。 string :connect_title do title.join('').gsub(/\s/, '').downcase end string :connect_creator do creator.join('').gsub(/\s/, '').downcase end string :connect_publisher do publisher.join('').gsub(/\s/, '').downcase end string :isbn, multiple: true do isbn_characters end string :issn, multiple: true do if series_statements.exists? [identifier_contents(:issn), (series_statements.map{|s| s.manifestation.identifier_contents(:issn)})].flatten.uniq.compact else identifier_contents(:issn) end end string :lccn, multiple: true do identifier_contents(:lccn) end string :jpno, multiple: true do identifier_contents(:jpno) end string :carrier_type do carrier_type.name end string :library, multiple: true do if series_master? root_series_statement.root_manifestation.items.map{|i| i.shelf.library.name}.flatten.uniq else items.map{|i| i.shelf.library.name} end end string :language do language.try(:name) end string :item_identifier, multiple: true do if series_master? root_series_statement.root_manifestation.items.pluck(:item_identifier, :binding_item_identifier).flatten.compact else items.pluck(:item_identifier, :binding_item_identifier).flatten.compact end end string :shelf, multiple: true do items.collect{|i| "#{i.shelf.library.name}_#{i.shelf.name}"} end time :created_at time :updated_at time :deleted_at time :pub_date, multiple: true do if series_master? root_series_statement.root_manifestation.pub_dates else pub_dates end end time :date_of_publication integer :pub_year do date_of_publication.try(:year) end integer :creator_ids, multiple: true integer :contributor_ids, multiple: true integer :publisher_ids, multiple: true integer :item_ids, multiple: true integer :original_manifestation_ids, multiple: true integer :parent_ids, multiple: true do original_manifestations.pluck(:id) end integer :required_role_id integer :height integer :width integer :depth integer :volume_number integer :issue_number integer :serial_number integer :start_page integer :end_page integer :number_of_pages float :price integer :series_statement_ids, multiple: true boolean :repository_content # for OpenURL text :aulast do creators.pluck(:last_name) end text :aufirst do creators.pluck(:first_name) end # OTC start string :creator, multiple: true do creator.map{|au| au.gsub(' ', '')} end text :au do creator end text :atitle do if serial? && root_series_statement.nil? titles end end text :btitle do title unless serial? end text :jtitle do if serial? if root_series_statement root_series_statement.titles else titles end end end text :isbn do # 前方一致検索のためtext指定を追加 isbn_characters end text :issn do # 前方一致検索のためtext指定を追加 if series_statements.exists? [identifier_contents(:issn), (series_statements.map{|s| s.manifestation.identifier_contents(:issn)})].flatten.uniq.compact else identifier_contents(:issn) end end string :sort_title string :doi, multiple: true do identifier_contents(:doi) end boolean :serial do serial? end boolean :series_master do series_master? end boolean :resource_master do if serial? if series_master? true else false end else true end end time :acquired_at end if ENV['ENJU_STORAGE'] == 's3' has_attached_file :attachment, storage: :s3, s3_credentials: { access_key: ENV['AWS_ACCESS_KEY_ID'], secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], bucket: ENV['S3_BUCKET_NAME'], s3_host_name: ENV['S3_HOST_NAME'], s3_region: ENV["S3_REGION"] }, s3_permissions: :private else has_attached_file :attachment, path: ":rails_root/private/system/:class/:attachment/:id_partition/:style/:filename" end do_not_validate_attachment_file_type :attachment validates_presence_of :original_title, :carrier_type, :language validates_associated :carrier_type, :language validates :start_page, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :end_page, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :height, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :width, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :depth, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :manifestation_identifier, uniqueness: true, allow_blank: true validates :pub_date, format: {with: /\A\[{0,1}\d+([\/-]\d{0,2}){0,2}\]{0,1}\z/}, allow_blank: true validates :access_address, url: true, allow_blank: true, length: {maximum: 255} validates :issue_number, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :volume_number, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :serial_number, numericality: {greater_than_or_equal_to: 0}, allow_blank: true validates :edition, numericality: {greater_than_or_equal_to: 0}, allow_blank: true after_create :clear_cached_numdocs before_save :set_date_of_publication, :set_number after_save :index_series_statement, :extract_text! after_destroy :index_series_statement after_touch do |manifestation| manifestation.index manifestation.index_series_statement Sunspot.commit end strip_attributes only: [:manifestation_identifier, :pub_date, :original_title] paginates_per 10 attr_accessor :during_import, :parent_id def set_date_of_publication return if pub_date.blank? year = Time.utc(pub_date.rjust(4, "0")).year rescue nil begin date = Time.zone.parse(pub_date.rjust(4, "0")) if date.year != year raise ArgumentError end rescue ArgumentError, TZInfo::AmbiguousTime date = nil end pub_date_string = pub_date.rjust(4, "0").split(';').first.gsub(/[\[\]]/, '') if pub_date_string.length == 4 date = Time.zone.parse(Time.utc(pub_date_string).to_s).beginning_of_day else while date.nil? do pub_date_string += '-01' break if pub_date_string =~ /-01-01-01$/ begin date = Time.zone.parse(pub_date_string) if date.year != year raise ArgumentError end rescue ArgumentError date = nil rescue TZInfo::AmbiguousTime date = nil self.year_of_publication = pub_date_string.to_i if pub_date_string =~ /^\d+$/ break end end end if date self.year_of_publication = date.year self.month_of_publication = date.month if date.year > 0 self.date_of_publication = date end end end def self.cached_numdocs Rails.cache.fetch("manifestation_search_total"){Manifestation.search.total} end def clear_cached_numdocs Rails.cache.delete("manifestation_search_total") end def parent_of_series original_manifestations end def number_of_pages if start_page && end_page end_page.to_i - start_page.to_i + 1 end end def titles title = [] title << original_title.to_s.strip title << title_transcription.to_s.strip title << title_alternative.to_s.strip title << volume_number_string title << issue_number_string title << serial_number.to_s title << edition_string title << series_statements.map{|s| s.titles} #title << original_title.wakati #title << title_transcription.wakati rescue nil #title << title_alternative.wakati rescue nil title.flatten end def url #access_address "#{LibraryGroup.site_config.url}#{self.class.to_s.tableize}/#{self.id}" end def creator creators.collect(&:name).flatten end def contributor contributors.collect(&:name).flatten end def publisher publishers.collect(&:name).flatten end def title titles end # TODO: よりよい推薦方法 def self.pickup(keyword = nil, current_user = nil) return nil if self.cached_numdocs < 5 if current_user.try(:role) current_role_id = current_user.role.id else current_role_id = 1 end # TODO: ヒット件数が0件のキーワードがあるときに指摘する response = Manifestation.search do fulltext keyword if keyword with(:required_role_id).less_than_or_equal_to current_role_id order_by(:random) paginate page: 1, per_page: 1 end response.results.first end def extract_text return nil if attachment.path.nil? return nil unless ENV['ENJU_EXTRACT_TEXT'] == 'true' if ENV['ENJU_STORAGE'] == 's3' body = Faraday.get(attachment.expiring_url(10)).body.force_encoding('UTF-8') else body = File.open(attachment.path).read end client = Faraday.new(url: ENV['SOLR_URL'] || Sunspot.config.solr.url) do |conn| conn.request :multipart conn.adapter :net_http end response = client.post('update/extract?extractOnly=true&wt=json&extractFormat=text') do |req| req.headers['Content-type'] = 'text/html' req.body = body end update_column(:fulltext, JSON.parse(response.body)[""]) end def extract_text! extract_text index Sunspot.commit end def created(agent) creates.where(agent_id: agent.id).first end def realized(agent) realizes.where(agent_id: agent.id).first end def produced(agent) produces.where(agent_id: agent.id).first end def sort_title if series_master? if root_series_statement.title_transcription? NKF.nkf('-w --katakana', root_series_statement.title_transcription) else root_series_statement.original_title end else if title_transcription? NKF.nkf('-w --katakana', title_transcription) else original_title end end end def self.find_by_isbn(isbn) identifier_type = IdentifierType.where(name: 'isbn').first return nil unless identifier_type Manifestation.includes(identifiers: :identifier_type).where(:"identifiers.body" => isbn, :"identifier_types.name" => 'isbn') end def index_series_statement series_statements.map{|s| s.index; s.root_manifestation.try(:index)} end def acquired_at items.order(:acquired_at).first.try(:acquired_at) end def series_master? return true if root_series_statement false end def web_item items.where(shelf_id: Shelf.web.id).first end def set_agent_role_type(agent_lists, options = {scope: :creator}) agent_lists.each do |agent_list| name_and_role = agent_list[:full_name].split('||') if agent_list[:agent_identifier].present? agent = Agent.where(agent_identifier: agent_list[:agent_identifier]).first end agent = Agent.where(full_name: name_and_role[0]).first unless agent next unless agent type = name_and_role[1].to_s.strip case options[:scope] when :creator type = 'author' if type.blank? role_type = CreateType.where(name: type).first create = Create.where(work_id: id, agent_id: agent.id).first if create create.create_type = role_type create.save(validate: false) end when :publisher type = 'publisher' if role_type.blank? produce = Produce.where(manifestation_id: id, agent_id: agent.id).first if produce produce.produce_type = ProduceType.where(name: type).first produce.save(validate: false) end else raise "#{options[:scope]} is not supported!" end end end def set_number self.volume_number = volume_number_string.scan(/\d*/).map{|s| s.to_i if s =~ /\d/}.compact.first if volume_number_string && !volume_number? self.issue_number = issue_number_string.scan(/\d*/).map{|s| s.to_i if s =~ /\d/}.compact.first if issue_number_string && !issue_number? self.edition = edition_string.scan(/\d*/).map{|s| s.to_i if s =~ /\d/}.compact.first if edition_string && !edition? end def pub_dates return [] unless pub_date pub_date_array = pub_date.split(';') pub_date_array.map{|pub_date_string| date = nil while date.nil? do pub_date_string += '-01' break if pub_date_string =~ /-01-01-01$/ begin date = Time.zone.parse(pub_date_string) rescue ArgumentError rescue TZInfo::AmbiguousTime end end date }.compact end def latest_issue if series_master? derived_manifestations.where('date_of_publication IS NOT NULL').order('date_of_publication DESC').first end end def first_issue if series_master? derived_manifestations.where('date_of_publication IS NOT NULL').order('date_of_publication DESC').first end end def identifier_contents(name) if Rails::VERSION::MAJOR > 3 identifiers.id_type(name).order(:position).pluck(:body) else identifier_type = IdentifierType.where(name: name).first if identifier_type identifiers.where(identifier_type_id: identifier_type.id).order(:position).pluck(:body) else [] end end end def self.csv_header(role, options = {col_sep: "\t", role: :Guest}) header = %w( manifestation_id original_title title_transcription creator contributor publisher pub_date statement_of_responsibility manifestation_price manifestation_created_at manifestation_updated_at manifestation_identifier access_address description note extent dimensions carrier_type edition edition_string volume_number volume_number_string issue_number issue_number_string serial_number ) header += IdentifierType.order(:position).pluck(:name) if defined?(EnjuSubject) header += SubjectHeadingType.order(:position).pluck(:name).map{|type| "subject:#{type}"} header += ClassificationType.order(:position).pluck(:name).map{|type| "classification:#{type}"} end header += %w( item_id item_identifier call_number item_note ) case role.to_sym when :Administrator, :Librarian header << "item_price" end header += %w( acquired_at accepted_at ) case role.to_sym when :Administrator, :Librarian header += %w( bookstore budget_type total_checkouts ) end header += %w( circulation_status shelf library item_created_at item_updated_at ) header.to_csv(options) end def to_csv(options = {format: :txt, role: :Guest}) lines = [] if items.exists? items.includes(shelf: :library).each do |i| item_lines = [] item_lines << id item_lines << original_title item_lines << title_transcription if creators.exists? item_lines << creators.pluck(:full_name).join("//") else item_lines << nil end if contributors.exists? item_lines << contributors.pluck(:full_name).join("//") else item_lines << nil end if publishers.exists? item_lines << publishers.pluck(:full_name).join("//") else item_lines << nil end item_lines << pub_date item_lines << statement_of_responsibility item_lines << price item_lines << created_at item_lines << updated_at item_lines << manifestation_identifier item_lines << access_address item_lines << description.try(:gsub, /\r?\n/, '\n') item_lines << note.try(:gsub, /\r?\n/, '\n') item_lines << extent item_lines << dimensions item_lines << carrier_type.name item_lines << edition item_lines << edition_string item_lines << volume_number item_lines << volume_number_string item_lines << issue_number item_lines << issue_number_string item_lines << serial_number IdentifierType.order(:position).pluck(:name).each do |identifier_type| if identifier_contents(identifier_type.to_sym).first item_lines << identifier_contents(identifier_type.to_sym).first else item_lines << nil end end if defined?(EnjuSubject) SubjectHeadingType.order(:position).each do |subject_heading_type| if subjects.exists? item_lines << subjects.where(subject_heading_type: subject_heading_type).pluck(:term).join('//') else item_lines << nil end end ClassificationType.order(:position).each do |classification_type| if classifications.exists? item_lines << classifications.where(classification_type: classification_type).pluck(:category).join('//') else item_lines << nil end end end item_lines << i.id item_lines << i.item_identifier item_lines << i.call_number item_lines << i.note.try(:gsub, /\r?\n/, '\n') case options[:role].to_sym when :Administrator, :Librarian item_lines << i.price end item_lines << i.acquired_at item_lines << i.accept.try(:created_at) case options[:role].to_sym when :Administrator, :Librarian item_lines << i.bookstore.try(:name) item_lines << i.budget_type.try(:name) item_lines << Checkout.where(:item_id => i.id).size end item_lines << i.circulation_status.try(:name) item_lines << i.shelf.name item_lines << i.shelf.library.name item_lines << i.created_at item_lines << i.updated_at lines << item_lines end else line = [] line << id line << original_title line << title_transcription if creators.exists? line << creators.pluck(:full_name).join("//") else line << nil end if contributors.exists? line << contributors.pluck(:full_name).join("//") else line << nil end if publishers.exists? line << publishers.pluck(:full_name).join("//") else line << nil end line << pub_date line << statement_of_responsibility line << price line << created_at line << updated_at line << manifestation_identifier line << access_address line << description.try(:gsub, /\r?\n/, '\n') line << note.try(:gsub, /\r?\n/, '\n') line << extent line << dimensions line << carrier_type.name line << edition line << edition_string line << volume_number line << volume_number_string line << issue_number line << issue_number_string line << serial_number IdentifierType.order(:position).pluck(:name).each do |identifier_type| if identifier_contents(identifier_type.to_sym).first line << identifier_contents(identifier_type.to_sym).first else line << nil end end if defined?(EnjuSubject) SubjectHeadingType.order(:position).each do |subject_heading_type| if subjects.exists? line << subjects.where(subject_heading_type: subject_heading_type).pluck(:term).join('//') else line << nil end end ClassificationType.order(:position).each do |classification_type| if classifications.exists? line << classifications.where(classification_type: classification_type).pluck(:category).join('//') else line << nil end end end lines << line end if options[:format] == :txt lines.map{|i| i.to_csv(col_sep: "\t")}.join else lines end end def self.export(options = {format: :txt, role: :Guest}) file = '' file += Manifestation.csv_header(options[:role], col_sep: "\t") if options[:format].to_sym == :txt Manifestation.find_each do |manifestation| file += manifestation.to_csv(options) end file end def root_series_statement series_statements.where(root_manifestation_id: id).first end def isbn_characters identifier_contents(:isbn).map{|i| isbn10 = isbn13 = isbn10_dash = isbn13_dash = nil isbn10 = Lisbn.new(i).isbn10 isbn13 = Lisbn.new(i).isbn13 isbn10_dash = Lisbn.new(isbn10).isbn_with_dash if isbn10 isbn13_dash = Lisbn.new(isbn13).isbn_with_dash if isbn13 [ isbn10, isbn13, isbn10_dash, isbn13_dash ] }.flatten end end # == Schema Information # # Table name: manifestations # # id :integer not null, primary key # original_title :text not null # title_alternative :text # title_transcription :text # classification_number :string # manifestation_identifier :string # date_of_publication :datetime # date_copyrighted :datetime # created_at :datetime # updated_at :datetime # deleted_at :datetime # access_address :string # language_id :integer default(1), not null # carrier_type_id :integer default(1), not null # start_page :integer # end_page :integer # height :integer # width :integer # depth :integer # price :integer # fulltext :text # volume_number_string :string # issue_number_string :string # serial_number_string :string # edition :integer # note :text # repository_content :boolean default(FALSE), not null # lock_version :integer default(0), not null # required_role_id :integer default(1), not null # required_score :integer default(0), not null # frequency_id :integer default(1), not null # subscription_master :boolean default(FALSE), not null # attachment_file_name :string # attachment_content_type :string # attachment_file_size :integer # attachment_updated_at :datetime # title_alternative_transcription :text # description :text # abstract :text # available_at :datetime # valid_until :datetime # date_submitted :datetime # date_accepted :datetime # date_captured :datetime # pub_date :string # edition_string :string # volume_number :integer # issue_number :integer # serial_number :integer # content_type_id :integer default(1) # year_of_publication :integer # attachment_meta :text # month_of_publication :integer # fulltext_content :boolean # serial :boolean # statement_of_responsibility :text # publication_place :text # extent :text # dimensions :text #