#!/usr/bin/env ruby

require 'flexirecord'

# Copyright (c) 2007 FlexiGuided GmbH, Berlin
#
# Author: Jan Behrens
#
# Website: http://www.flexiguided.de/publications.flexirecord.en.html
#
# -----
#
# Demonstration module for FlexiRecord.

module FlexiRecordDemo

  # FlexiRecord::ConnectionPool used for all models in this module.
  ConnectionPool = FlexiRecord::BaseRecord.connection_pool = FlexiRecord::ConnectionPool.new(:engine => :postgresql, :db => 'moviedemo')

  # A person (demo class).
  #
  # CREATE TABLE "person" ("id" serial8 primary key, "name" text not null );
  class Person < FlexiRecord::BaseRecord
    self.table_name = 'person'
  end

  # A Medium (demo class).
  #
  # CREATE TABLE "medium" ("number" int8 primary key, "lent_to_id" int8 references "person" ("id") on delete restrict on update cascade );
  class Medium < FlexiRecord::BaseRecord
    self.table_name = 'medium'
    add_many_to_one_reference Person, ['lent_to_id', 'id'], :lent_to, :borrowed_media
    def self.after_select(records)
      super
      records.preload(:entries).preload(:movie)
      records.preload(:lent_to)
      records.preload(:movies)
    end
    def save
      if self.number == :auto
        self.class.transaction self, :read_committed do
          self.class.db_execute("LOCK TABLE #{self.class.table} IN SHARE ROW EXCLUSIVE MODE")
          last_medium = Medium.select1('ORDER BY "number" DESC LIMIT 1')
          self.number = if last_medium
            last_medium.number + 1
          else
            1
          end
          return super
        end
      else
        return super
      end
    end
    def available?
      lent_to.nil?
    end
  end

  # A movie (demo class).
  #
  # CREATE TABLE "movie" ("id" serial8 primary key, "name" text not null );
  class Movie < FlexiRecord::BaseRecord
    self.table_name = 'movie'
  end

  # A medium entry (demo class).
  #
  # CREATE TABLE "medium_entry" ("medium_number" int8 not null references "medium" ("number") on delete cascade on update cascade, "position" int8 not null, "movie_id" int8 not null references "movie" ("id") on delete restrict on update cascade, PRIMARY KEY ("medium_number", "position") );
  class MediumEntry < FlexiRecord::BaseRecord
    include FlexiRecord::ListRecord
    self.table_name = 'medium_entry'
    add_connected_references(
      Medium, 'medium_', :medium, :entries, :movies,
      Movie, 'movie_', :movie, :movie_entries, :media
    )
    Medium.add_read_option :entries, :default, 'ORDER BY "position"'
    Medium.add_read_option :movies,  :default, 'ORDER BY "rel"."position"'
  end

  # A rating entry (demo class).
  #
  # CREATE TABLE "rating" ("person_id" int8 not null references "person" ("id") on delete cascade on update cascade, "movie_id" int8 not null references "movie" ("id") on delete cascade on update cascade, "rating" numeric, "comment" text, PRIMARY KEY ("person_id", "movie_id") );
  class Rating < FlexiRecord::Relationship
    self.table_name = 'rating'
    add_connected_references(
      Person, 'person_', :person, :ratings, :rated_movies,
      Movie, ['movie_id', 'id'], :movie, :ratings, :rated_by
    )
  end


  # A small demonstration program. In order to be run, a database named 'moviedemo' has to be installed and initialized with the 'flexirecord-demo.sql' file, which is shipped with the software package.
  def demo
    # Creating demo entries
    Person.transaction do
      Rating.db_execute(     "DELETE FROM #{Rating.table}"     )
      MediumEntry.db_execute("DELETE FROM #{MediumEntry.table}")
      Movie.db_execute(      "DELETE FROM #{Movie.table}"      )
      Medium.db_execute(     "DELETE FROM #{Medium.table}"     )
      Person.db_execute(     "DELETE FROM #{Person.table}"     )
    end
    anja    = Person.new(:name => 'Anja'   ).save
    phillip = Person.new(:name => 'Phillip').save
    wilson  = Person.new(:name => 'Wilson' ).save
    american_beauty = Movie.new(:name => 'American Beauty').save
    naruto          = Movie.new(:name => 'Naruto').save
    koyaanisqatsi   = Movie.new(:name => 'Koyaanisqatsi').save
    medium_a = nil
    Medium.transaction do
      medium_a = Medium.new(:number => :auto).save
      FlexiRecordDemo::MediumEntry.new(:medium => medium_a, :position => :last, :movie => naruto).save
    end
    medium_b = nil
    Medium.transaction do
      medium_b = Medium.new(:number => '42', :lent_to => phillip).save
      MediumEntry.new(:medium => medium_b, :position => :last, :movie => koyaanisqatsi).save
      MediumEntry.new(:medium => medium_b, :position => :last, :movie => american_beauty).save
    end
    Rating.new(:person => anja, :movie => american_beauty, :rating => Rational(7, 10)).save
    Rating.new(:person => anja, :movie => koyaanisqatsi, :rating => Rational(9,10)).save
    Rating.new(:person => phillip, :movie => koyaanisqatsi, :rating => Rational(6,10)).save
    Rating.new(:person => phillip, :movie => koyaanisqatsi, :rating => Rational(8,10)).save
    Rating.new(:person => wilson, :movie => naruto, :comment => 'Rasengan!').save
    # Some queries
    person = Person.select1('WHERE "name" ILIKE $ ORDER BY "name" DESC LIMIT 1', 'P%')
    puts "First person whose name is starting with 'P' is: #{person.name}."
    puts "The following media are borrowed by him/her:"
    person.borrowed_media.each do |medium|
      puts "- ##{medium.number}"
      medium.entries.each do |entry|
        puts "  - #{entry.movie.name}"
      end
    end
    puts "He rated the following movies:"
    person.rated_movies.each do |movie|
      rating = movie.rel
      puts "- #{movie.name}"
      puts "  - Rating: #{rating.rating ? rating.rating.to_s : 'none'}"
      puts "  - Comment: #{rating.comment || 'none'}"
    end
    anjas = Person.select('WHERE "name" = $', 'Anja')
    if anjas.length == 1
      anja = anjas.first
      anjas_favourite = anja.rated_movies('WHERE "rel"."rating" NOTNULL ORDER BY "rel"."rating" DESC LIMIT 1').first
      puts "The movie, which Anja likes most is: #{anjas_favourite ? anjas_favourite.name : 'N/A'}."
    else
      puts "There is more than one person named Anja."
    end
    nil
  end
  module_function :demo

end

