<repo_name>markhao-cb/BDMI<file_sep>/docs/phases/phase2.md
# Phase 2: Viewing Movie Info and Adding Comments

## Rails
### Models

### Controllers
Api::CommentsController (create, destroy, show, update)

### Views
* movies/show.json.jbuilder

## Backbone
### Models
* Movie (parses nested `comments` association)
* Comment

### Collections
* Comments

### Views
* CommentForm
* MovieShow (composite view, contains CommentsIndex subview)
* CommentsIndex (composite view, contains CommentsIndexItem subviews)
* CommentsIndexItem

## Gems/Libraries
<file_sep>/app/views/api/genres/genre_index.json.jbuilder
json.array! @movies.results do |movie|
  unless movie["poster_path"].nil?
    json.extract!(
      movie,
      'id',
      'title',
      'release_date',
      'vote_average',
      'vote_count',
      'popularity',
      'poster_path',
      'overview'
    )
    json.set! :genre_name, @genre.name
    json.set! :page_num, @movies.page
    json.set! :base_url, @config.base_url
    json.set! :genres do
      json.array! @genres do |genre|
        json.partial! 'api/genres/genre', genre: genre
      end
    end
  end
end
<file_sep>/app/assets/javascripts/models/user.js
BDMI.Models.User = Backbone.Model.extend({
  urlRoot: '/api/users',

  parse: function(payload) {
    if (payload.reviews) {
      this.reviews().set(payload.reviews);
      delete payload.reviews;
    }

    if (payload.watched_movies) {
      this.watchedMovies().set(payload.watched_movies);
      delete payload.watched_movies;
    }

    if (payload.wanted_movies) {
      this.wantedMovies().set(payload.wanted_movies);
      delete payload.wanted_movies;
    }
    return payload;
  },

  reviews: function() {
    if (this._reviews === undefined) {
      this._reviews = new BDMI.Collections.Reviews();
    }
    return this._reviews;
  },

  watchedMovies: function() {
    if(this._watchedMovies === undefined) {
      this._watchedMovies = new BDMI.Collections.InTheatersMovies();
    }
    return this._watchedMovies;
  },

  wantedMovies: function() {
    if(this._wantedMovies === undefined) {
      this._wantedMovies = new BDMI.Collections.InTheatersMovies();
    }
    return this._wantedMovies;
  },
});
<file_sep>/app/views/api/movies/_movie_show.json.jbuilder
json.extract!(
  movie,
  :id,
  :title,
  :release_date,
  :vote_average,
  :vote_count,
  :popularity,
  :revenue,
  :runtime,
  :overview,
  :budget,
  :tagline,
  :imdb_id,
)
if display_score
  json.set! :votes, votes
  json.set! :vote_score, score
end

if display_user
  json.set! :in_watched, watched
  json.set! :in_wanted, wanted
end

if display_images
  json.images do
    json.array! movie.images do |image|
      json.partial! 'api/movies/image', image: image
    end
  end
end

if display_reviews
  json.reviews do
    json.array! movie.reviews.includes(:author).includes(:likes) do |review|
      json.partial! 'api/reviews/review', review: review
    end
  end
end

if display_posters
  json.posters do
    json.array! movie.posters do |poster|
      json.partial! 'api/movies/poster', poster: poster
    end
  end
end

if display_actors
  json.actors do
    json.array! movie.actors.includes(:castings, :images, :likes) do |actor|
      json.partial! 'api/actors/actor', actor: actor, display_images: true, display_casting: true, movie_id: movie.id
    end
  end
end

if display_genres
  json.genres do
    json.array! movie.genres do |genre|
      json.partial! 'api/genres/genre', genre: genre
    end
  end
end
<file_sep>/app/controllers/api/wanteds_controller.rb
class Api::WantedsController < ApplicationController
  def create
    wanted = Wantwatchmovie.find_by(user_id: current_user.id, movie_id: params[:movie_id])
    unless wanted
      wanted = current_user.want_watchs.new(movie_id: params[:movie_id])
      if wanted.save
        render json: wanted
      else
        render json: { error: wanted.errors.full_messages }, status: 422
      end
    else
      render json: wanted
    end
  end

  def destroy
    wantwatch = Watchedmovie.find(params[:id])
    wantwatch.destroy
    render json: wantwatch
  end
end
<file_sep>/docs/phases/phase3.md
# Phase 3: Searching for Movies By Title

## Rails
### Models

### Controllers
Api::MoviesController (search)

### Views

## Backbone
### Models

### Collections

### Views
* MovieSearchItem
* SearchShow (composite view, contains MoviesIndex subviews)

## Gems/Libraries
<file_sep>/app/assets/javascripts/views/actors/casts_item.js
BDMI.Views.CastsItem = Backbone.CompositeView.extend({
  template: JST['movie/in_theaters_item'],

  className: "in_theaters_item",

  events: {
    "click .small_image":"handleClick",
    "hover":"handleHover",
    "mouseenter .small_image": "handleEnter",
    "mouseleave .small_image": "handleLeave"
  },

  initialize: function() {
    this.listenTo(this.model, 'sync change', this.render);
  },

  render: function() {
    var poster_url = this.model.attributes.base_url + "original" +
    this.model.attributes.poster_path;
    var content = this.template({
      movie: this.model,
      poster_url: poster_url
    });
    this.$el.html(content);
    this.generateStars();
    return this;
  },

  handleClick: function(event) {
    this.$el.addClass('animated flipOutX');
    this.$el.one('webkitAnimationEnd', function() {
      this.movieShow();
      this.render();
    }.bind(this));
  },

  movieShow: function() {
    window.scrollTo(0, 0);
    Backbone.history.navigate("movies/"+this.model.id, { trigger: true });
  },

  handleEnter: function(event) {
    $(event.currentTarget).addClass('animated infinite pulse');
  },

  handleLeave: function(event) {
    $(event.currentTarget).removeClass('animated infinite pulse');
  },


  generateStars: function() {
        this.$('.small_movie_score').empty();
        var grade = Math.floor(this.model.get('vote_score') / 2);
        var star = Math.max(0, (Math.min(5, grade)));
        var blank = 5 - star;
        while (star > 0) {
          var $star = $("<span></span>");
          $star.text("★");
          this.$('.small_movie_score').append($star);
          star--;
        }
        while (blank > 0) {
          var $blank = $("<span></span>");
          $blank.text("☆");
          this.$('.small_movie_score').append($blank);
          blank--;
        }
      }
});
<file_sep>/app/assets/javascripts/views/movies/carousel.js
BDMI.Views.CarouselItemView = Backbone.CompositeView.extend({
  template: JST['movie/carousel'],

  className: "item",

  events: {
    "click": "show"
  },

  initialize: function() {
    this.listenTo(this.model, 'sync', this.render);
  },

  render: function() {
    var image_url = this.model.images().first().attributes.image_url.slice(0,48)+
    "/c_lpad,w_800"+this.model.images().first().attributes.image_url.slice(48);
    var content = this.template({ image_url: image_url });
    this.$el.html(content);
    return this;
  },

  show: function(event) {
    Backbone.history.navigate("movies/" + this.model.id, { trigger: true} );
  }
});
<file_sep>/app/assets/javascripts/models/trailer.js
BDMI.Models.Trailer = Backbone.Model.extend({
  urlRoot: "/api/trailer"
});
<file_sep>/docs/views.md
# View Wireframes

## New Session
![new-session]

## Movie Index
![movie-index]

## Movie Show
![movie-show]

## Comment Form
![comment-form]


[new-session]: ./wireframes/new_session.png
[movie-index]: ./wireframes/movie_index.png
[movie-show]: ./wireframes/movie_show.png
[comment-form]: ./wireframes/comment_form.png
<file_sep>/app/assets/javascripts/collections/posters.js
BDMI.Collections.MoviePosters = Backbone.Collection.extend({
  model: BDMI.Models.MoviePoster
});
<file_sep>/app/views/api/movies/_poster.json.jbuilder
json.extract!(poster, :id, :poster_url)
<file_sep>/db/seeds.rb
Tmdb::Api.key(ENV['THEMOVIEDB_API_KEY'])
auth = {
  cloud_name: ENV['CLOUD_NAME'],
  api_key: ENV['CLOUD_API_KEY'],
  api_secret: ENV['CLOUD_API_SECRET'],
  upload_preset: ENV['UPLOAD_PRESET']
};
config = Tmdb::Configuration.new

#-------------------------Genres---------------------------
# genres = Tmdb::Genre.list["genres"]
# genres.each do |genre|
#   id = genre["id"]
#   name = genre["name"]
#   Genre.create(id:id, name:name)
# end

in_theaters_movies = Tmdb::Movie.now_playing # API called here !!
top_rated_movies = Tmdb::Movie.top_rated

#-------------------------Movies---------------------------
in_theaters_movies.each do |m|
  unless Movie.find_by(id: m["id"])
    movie = Tmdb::Movie.detail(m["id"])
    id = movie["id"]
    title = movie["title"]
    release_date = movie["release_date"]
    vote_average = movie["vote_average"]
    vote_count = movie["vote_count"]
    popularity = movie["popularity"]
    overview = movie["overview"]
    runtime = movie["runtime"]
    imdb_id = movie["imdb_id"]
    tagline = movie["tagline"]
    budget = movie["budget"]
    revenue = movie["revenue"]

    unless movie['backdrop_path'].nil? || movie['poster_path'].nil? ||
           vote_count == 0 || release_date.nil?

      backdrop_path = "#{config.base_url}original#{movie["backdrop_path"]}"
      backdrop = Cloudinary::Uploader.upload(backdrop_path,auth)
      image_url = backdrop["url"]
      poster_path = "#{config.base_url}original#{movie["poster_path"]}"
      poster = Cloudinary::Uploader.upload(poster_path,auth)
      poster_url = poster["url"]


      newMovie = Movie.create(
                  id: id,
                  title: title,
                  release_date: release_date,
                  vote_average: vote_average,
                  vote_count: vote_count,
                  popularity: popularity,
                  overview: overview,
                  runtime: runtime,
                  imdb_id: imdb_id,
                  tagline: tagline,
                  budget: budget,
                  revenue: revenue
                )


    #---------------------------Taggings---------------------------


      movie["genres"].each do |genre|
        genre_id = genre["id"]
        Tagging.create(genre_id:genre_id, movie_id:movie["id"])
      end


    #---------------------------Images---------------------------

      newMovie.posters.create(poster_url:poster_url)
      newMovie.images.create(image_url:image_url)

      casts = Tmdb::Movie.casts(newMovie.id)
      casts.each do |actor|

        if actor["order"] < 11
          person = Tmdb::Person.detail(actor["id"])

          if person["profile_path"] != nil

            if Actor.find_by(id:person["id"]) == nil
              newActor = Actor.create!(
                            id:person["id"],
                            name:person["name"],
                            place_of_birth:person["place_of_birth"],
                            birthday:person["birthday"]
                          )

              profile_path = "#{config.base_url}original#{person["profile_path"]}"
              profile = Cloudinary::Uploader.upload(profile_path,auth)
              image_url = profile["url"]

              newActor.images.create!(image_url:image_url)
            end

            Casting.create!(
              actor_id:person["id"],
              movie_id:newMovie.id,
              ord: actor["order"],
              act_as: actor["character"]
            )
          end
        end
      end
    end
  end
end

top_rated_movies.take(10).each do |m|
  unless Movie.find_by(id: m["id"])
    movie = Tmdb::Movie.detail(m["id"])
    id = movie["id"]
    title = movie["title"]
    release_date = movie["release_date"]
    vote_average = movie["vote_average"]
    vote_count = movie["vote_count"]
    popularity = movie["popularity"]
    overview = movie["overview"]
    runtime = movie["runtime"]
    imdb_id = movie["imdb_id"]
    tagline = movie["tagline"]
    budget = movie["budget"]
    revenue = movie["revenue"]

    unless movie['backdrop_path'].nil? || movie['poster_path'].nil? ||
           vote_count == 0 || release_date.nil?

      backdrop_path = "#{config.base_url}original#{movie["backdrop_path"]}"
      backdrop = Cloudinary::Uploader.upload(backdrop_path,auth)
      image_url = backdrop["url"]
      poster_path = "#{config.base_url}original#{movie["poster_path"]}"
      poster = Cloudinary::Uploader.upload(poster_path,auth)
      poster_url = poster["url"]


      newMovie = Movie.create(
                  id: id,
                  title: title,
                  release_date: release_date,
                  vote_average: vote_average,
                  vote_count: vote_count,
                  popularity: popularity,
                  overview: overview,
                  runtime: runtime,
                  imdb_id: imdb_id,
                  tagline: tagline,
                  budget: budget,
                  revenue: revenue
                )


    #---------------------------Taggings---------------------------


      movie["genres"].each do |genre|
        genre_id = genre["id"]
        Tagging.create(genre_id:genre_id, movie_id:movie["id"])
      end


    #---------------------------Images---------------------------

      newMovie.posters.create(poster_url:poster_url)
      newMovie.images.create(image_url:image_url)

      casts = Tmdb::Movie.casts(newMovie.id)
      casts.each do |actor|

        if actor["order"] < 11
          person = Tmdb::Person.detail(actor["id"])

          if person["profile_path"] != nil

            if Actor.find_by(id:person["id"]) == nil
              newActor = Actor.create!(
                            id:person["id"],
                            name:person["name"],
                            place_of_birth:person["place_of_birth"],
                            birthday:person["birthday"]
                          )

              profile_path = "#{config.base_url}original#{person["profile_path"]}"
              profile = Cloudinary::Uploader.upload(profile_path,auth)
              image_url = profile["url"]

              newActor.images.create!(image_url:image_url)
            end

            Casting.create!(
              actor_id:person["id"],
              movie_id:newMovie.id,
              ord: actor["order"],
              act_as: actor["character"]
            )
          end
        end
      end
    end
  end
end



#---------------------------Actors---------------------------

# Movie.all.each do |dbmovie|
  #casts = Tmdb::Movie.casts(dbmovie.id)
  # casts.each do |actor|
  #
  #   if actor["order"] < 11
  #     person = Tmdb::Person.detail(actor["id"])
  #
  #     if person["profile_path"] != nil
  #
  #       if Actor.find_by(id:person["id"]) == nil
  #         newActor = Actor.create!(
  #                       id:person["id"],
  #                       name:person["name"],
  #                       place_of_birth:person["place_of_birth"],
  #                       birthday:person["birthday"]
  #                     )
  #
  #         profile_path = "#{config.base_url}original#{person["profile_path"]}"
  #         profile = Cloudinary::Uploader.upload(profile_path,auth)
  #         image_url = profile["url"]
  #
  #         newActor.images.create!(image_url:image_url)
  #       end
  #
  #       Casting.create!(
  #         actor_id:person["id"],
  #         movie_id:dbmovie.id,
  #         ord: actor["order"],
  #         act_as: actor["character"]
  #       )
  #     end
  #   end
  # end
# end
<file_sep>/app/models/tagging.rb
# == Schema Information
#
# Table name: taggings
#
#  id         :integer          not null, primary key
#  movie_id   :integer          not null
#  genre_id   :integer          not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Tagging < ActiveRecord::Base
  validates :movie, :genre, presence: true
  belongs_to :movie
  belongs_to :genre
end
<file_sep>/app/assets/javascripts/collections/searched_movies.js
BDMI.Collections.SearchedMovies = Backbone.Collection.extend({
  url: "/api/search_results",

  model: BDMI.Models.SearchedMovie,

  comparator: function(m) {
    return -m.get("vote_count");
  }
});
<file_sep>/app/views/api/actors/_actor_show.json.jbuilder
json.extract!(actor, :name, :birthday, :place_of_birth, :biography)

if display_images
  json.images do
    json.array! actor.images do |image|
      json.partial! 'api/movies/image', image: image
    end
  end
end

if display_movies
  json.credits do
    json.array! credits do |movie|
      unless movie['poster_path'].nil? || movie['media_type'] != 'movie'
        json.extract!(
          movie,
          'id',
          'character',
          'title',
          'release_date',
          'poster_path'
        )
        json.extract!(config, :base_url)
      end
    end
  end
end
<file_sep>/app/assets/javascripts/collections/reviews.js
BDMI.Collections.Reviews = Backbone.Collection.extend({
  url: "/api/reviews",
  model: BDMI.Models.Review,
  comparator: "created_at"
});
<file_sep>/app/views/api/users/_user_review.json.jbuilder
json.extract!(
  review,
  :id,
  :title,
  :body,
  :grade,
  :created_at,
  :updated_at,
  :author_id
)
json.set! :author_name, author_name
json.set! :movie, review.movie
json.set! :poster, review.movie.posters.first
<file_sep>/app/assets/javascripts/collections/top_rated_movies.js
BDMI.Collections.TopRatedMovies = Backbone.Collection.extend({
  url: "/api/top_rated_movies",
  model: BDMI.Models.TopRatedMovie
});
<file_sep>/app/assets/javascripts/views/movies/main.js
BDMI.Views.Main = Backbone.CompositeView.extend({
  template: JST.main,

  className: "main_container group",

  initialize: function() {
    this.addIntroView();
    this.addMovieInTheatersView();
    this.addTopRatedView();
    this.addSearchView();
  },

  addIntroView: function() {
    var introMovies = new BDMI.Collections.IntroMovies();
    var subview = new BDMI.Views.Intro({ collection: introMovies });
    this.addSubview('#hot',subview);
  },

  addMovieInTheatersView: function() {
    var movies = new BDMI.Collections.InTheatersMovies();
    var subview = new BDMI.Views.InTheaters({ collection: movies });
    this.addSubview('#new', subview);
  },

  addTopRatedView: function() {
    var movies = new BDMI.Collections.TopRatedMovies();
    var subview = new BDMI.Views.TopRatedView({ collection: movies });
    this.addSubview('#top',subview);
  },

  addSearchView: function() {
    var subview = new BDMI.Views.Search();
    this.addSubview('#search',subview);
  },

  render: function() {
    var content = this.template();
    this.$el.html(content);
    this.attachSubviews();
    return this;
  }

});
<file_sep>/app/assets/javascripts/collections/movie_images.js
BDMI.Collections.MovieImages = Backbone.Collection.extend({
  model: BDMI.Models.MovieImage
});
<file_sep>/app/assets/javascripts/views/movies/search.js
BDMI.Views.Search = Backbone.CompositeView.extend({
  template: JST['movie/search'],

  className: "search-section",

  events: {
    "submit form": "search"
  },

  render: function() {
    var content = this.template();
    this.$el.html(content);
    this.registerSearchListener();
    this.attachSubviews();
    return this;
  },

  search: function(event) {
    event.preventDefault();
    var formData = $(event.currentTarget).serializeJSON().search;
    if (formData[1].length < 2) {
      this.flashAlert(["Too short! Please be more specific."]);
    } else {
      if (formData[0] === "movie") {
        Backbone.history.navigate("search/movies/"+formData[1], { trigger: true });
      } else {
        Backbone.history.navigate("search/person/"+formData[1], { trigger: true });
      }
    }
  },

  registerSearchListener: function() {
    var typingTimer;                //timer identifier
    var doneTypingInterval = 500;  //time in ms, 0.5 seconds
    var $input = this.$('#myInput');
    //on keyup, start the countdown
    $input.on('paste keydown', function() {
      $('.search_dropdown_section').remove();
      clearTimeout(typingTimer);
      if ($input.val() !== "") {
        typingTimer = setTimeout(this.doneTyping.bind(this), doneTypingInterval);
      }
    }.bind(this));
  },

  getSelectionText: function() {
    var text = "";
    if (window.getSelection) {
        text = window.getSelection().toString();
    } else if (document.selection && document.selection.type != "Control") {
        text = document.selection.createRange().text;
    }
    return text;
  },

  doneTyping: function() {
    var formData = this.$('form').serializeJSON().search;
    if (formData[0] === "movie") {
      this.movieSearchDrop(formData[1]);
    } else {
      this.persionSearchDrop(formData[1]);
    }
  },

  movieSearchDrop: function(title) {
    var searchedMovies = new BDMI.Collections.SearchedMovies();
    searchedMovies.fetch({
      data: {
        title:title
      },
      processData: true,
      success: function(collection) {
        if (collection.length === 0) {
          $('.search_dropdown_section').remove();
        }
      }.bind(this)
    });
    var dropDownView = new BDMI.Views.SearchDropDown({
      collection: searchedMovies,
      type: 'movie'
    });
    this.addSubview('#dropdown',dropDownView);
  },

  persionSearchDrop: function(name) {
    var searchedPerson = new BDMI.Collections.MovieActors();
    var person = searchedPerson.fetch({
      data: {
        name: name
      },
      processData: true,
      success: function(collection) {
        if (collection.length === 0) {
          $('.search_dropdown_section').remove();
        }
      }.bind(this)
    });
    var dropDownView = new BDMI.Views.SearchDropDown({
      collection: searchedPerson,
      type: 'person'
    });
    this.addSubview('#dropdown',dropDownView);
  },

  flashAlert: function(messages) {
    var alertView = new BDMI.Views.AlertView({
      messages: messages
    });
    $('body').append(alertView.$el);
    alertView.render();
    alertView.$(".alert").addClass('animated fadeIn');
    setTimeout(function() {
      alertView.$(".alert").removeClass('fadeIn');
      alertView.$(".alert").addClass('fadeOut');
      alertView.$(".alert").one("webkitAnimationEnd", function() {
        alertView.remove();
      });
    },2000);
  }
});
<file_sep>/app/assets/javascripts/views/users/user_review_item.js
BDMI.Views.UserPageReviewItem = Backbone.CompositeView.extend({
  template: JST['users/user_review_item'],

  className: "review-item-user group",

  events: {
    "click #user-review-movie": "handleClick"
  },

  initialize: function() {
    this.listenTo(this.model, 'sync', this.render);
    this.addReviewView(this.model);
    this.poster_url = this.model.attributes.poster.poster_url;
  },

  addReviewView: function(review) {
    var subview = new BDMI.Views.Review({ model: review });
    this.addSubview("#user-review-content", subview);
  },

  render: function() {
    var content = this.template({
      poster_url: this.poster_url
    });
    this.$el.html(content);
    this.attachSubviews();
    return this;
  },

  handleClick: function() {
    window.scrollTo(0,0);
    Backbone.history.navigate('movies/'+this.model.attributes.movie.id, { trigger: true });
  }
});
<file_sep>/app/views/api/movies/movie_index.json.jbuilder
json.array! @movies do |movie|
  reviews = movie.reviews
  votes = movie.vote_count + movie.reviews.count
  vote_score = (movie.vote_average * movie.vote_count +
                reviews.sum(:grade) + reviews.count) / votes
  json.partial! 'movie_show',
                movie: movie,
                config: @config,
                score: vote_score,
                votes: votes,
                display_score: true,
                display_images: true,
                display_reviews: false,
                display_posters: true,
                display_actors: false,
                display_genres: false,
                display_user: false
end
<file_sep>/config/routes.rb
Rails.application.routes.draw do
  root to: 'roots#root'

  resources :users, only: [:new, :create]
  resource :session, only: [:new, :create, :destroy]

  namespace :api, defaults: { format: :json } do
    resources :users, only: [:index, :show]
    resources :movies, only: [:show]
    resources :reviews, only: [:index, :create, :update, :destroy]
    resources :actors, only: [:index, :show]
    resources :watcheds, only: [:create, :destroy]
    resources :wanteds, only: [:create, :destroy]
    resources :genres, only: [:index, :show]
    get '/intro_movies_index', to: 'movies#intro_movies_index'
    get '/in_theaters_movies', to: 'movies#in_theaters_movies_index'
    get '/top_rated_movies', to: 'movies#top_rated'
    get '/search_results', to: 'movies#search'
    get '/trailer', to: 'movies#search_trailer'
  end
end
<file_sep>/db/migrate/20150814184147_create_posters.rb
class CreatePosters < ActiveRecord::Migration
  def change
    create_table :posters do |t|
      t.string :poster_url, null: false
      t.integer :movie_id, null: false

      t.timestamps null: false
    end
    add_index :posters, :movie_id
  end
end
<file_sep>/app/controllers/api/watcheds_controller.rb
class Api::WatchedsController < ApplicationController
  def create
    watched = Watchedmovie.find_by(user_id: current_user.id, movie_id: params[:movie_id])
    unless watched
      watched = current_user.has_watcheds.new(movie_id: params[:movie_id])
      if watched.save
        render json: watched
      else
        render json: { error: watched.errors.full_messages }, status: 422
      end
    else
      render json: watched
    end
  end

  def destroy
    watched = Watchedmovie.find(params[:id])
    watched.destroy
    render json: watched
  end
end
<file_sep>/db/migrate/20150821085744_create_trailers.rb
class CreateTrailers < ActiveRecord::Migration
  def change
    create_table :trailers do |t|
      t.string :source, null: false
      t.integer :movie_id, null: false

      t.timestamps null: false
    end
    add_index :trailers, :movie_id, unique: true
  end
end
<file_sep>/app/controllers/api/actors_controller.rb
class Api::ActorsController < ApplicationController
  def index
    name = params[:name] || "Tom"
    @actors = Actor.search_actors_by_name(name)
    @config = Movie.find_config
    render 'actors'
  end

  def show
    person_id = params[:id]
    @credits = Actor.search_credits(person_id)['cast']
    @actor = Actor.find_by(id: person_id)
    if @actor.nil? || @actor.biography.nil?
      Actor.search_and_store_by_id(person_id)
      @actor = Actor.find_by(id: person_id)
    end
    @config = Movie.find_config
    render 'show'
  end
end
<file_sep>/app/assets/javascripts/views/users/user_show.js
BDMI.Views.UserShow = Backbone.CompositeView.extend({
  template: JST['users/user_show'],

  initialize: function() {
    this.listenTo(this.model, 'sync', this.render);
    this.addReviewView();
    this.addWatchListView();
    this.addWatchedListView();
    this.addPageScrollAnimation();
  },

  addReviewView: function() {
    var subview = new BDMI.Views.UserReviews({ collection: this.model.reviews() });
    this.addSubview('.user-reviews', subview);
  },

  addWatchListView: function() {
    var subview = new BDMI.Views.UserWatchList({ collection: this.model.wantedMovies() });
    this.addSubview('.user-watchlist', subview);
  },

  addWatchedListView: function() {
    var subview = new BDMI.Views.UserWatchedList({ collection: this.model.watchedMovies() });
    this.addSubview('.user-watched-list', subview);
  },

  render: function() {
    var content = this.template();
    this.$el.html(content);
    this.attachSubviews();
    return this;
  },

  addPageScrollAnimation: function() {
    $('a.page-scroll').bind('click', function(event) {
        if(Backbone.history.getFragment() !== "" &&
          !$(event.currentTarget).hasClass('user_login')) {
          Backbone.history.navigate("", { trigger: true });
        }
        var $anchor = $(this);
        if ($($anchor.attr('href')) !== []) {
          $('html, body').stop().animate({
              scrollTop: $($anchor.attr('href')).offset().top
          }, 1500, 'easeInOutExpo');
        }
        event.preventDefault();
    });
  }
});
<file_sep>/app/assets/javascripts/models/watched_movie.js
BDMI.Models.WatchedMovie = Backbone.Model.extend({
  urlRoot: "/api/watcheds"
});
<file_sep>/app/views/api/actors/actors.json.jbuilder
json.array! @actors do |actor|
  unless actor.profile_path.nil?
    json.extract!(
      actor,
      :id,
      :name,
      :profile_path
    )
    json.extract!(@config, :base_url)
  end
end
<file_sep>/app/views/api/reviews/_review.json.jbuilder
json.extract!(
  review,
  :id,
  :title,
  :body,
  :grade,
  :created_at,
  :updated_at,
  :author_id
)

json.set! :author_name, review.author.username
json.set! :num_likes, review.likes.count
<file_sep>/app/helpers/api/watcheds_helper.rb
module Api::WatchedsHelper
end
<file_sep>/app/helpers/api/wanteds_helper.rb
module Api::WantedsHelper
end
<file_sep>/app/views/api/movies/search.json.jbuilder
json.array! @movies do |movie|
  unless movie.poster_path.nil?
    json.extract!(
      movie,
      :id,
      :title,
      :release_date,
      :vote_average,
      :vote_count,
      :popularity,
      :poster_path,
      :overview
    )
    json.extract!(@config, :base_url)
  end
end
<file_sep>/app/assets/javascripts/views/reviews/review_form.js
BDMI.Views.ReviewForm = Backbone.View.extend({
  template: JST['reviews/review_form'],

  className: "review-form group",

  events: {
    "click #create-btn": "postReview",
    'click .my-background': 'handleRemove',
    'click .close-form': 'removeBtn',
    'click #cancel-btn': 'removeBtn'
  },

  initialize: function(options) {
     $(document).on('keyup', this.handleKey.bind(this));
     this.movie = options.movie;
     this.parentView = options.mainView;
  },

  handleKey: function (event) {
    if (event.keyCode === 27) {
      this.$(".my-background").remove();
      this.$(".my-content").addClass('animated fadeOut');
      this.$(".my-content").one('webkitAnimationEnd', function() {
        this.remove();
      }.bind(this));
    }
  },

  handleRemove: function (event) {
    this.$(".my-background").remove();
      this.$(".my-content").addClass('animated fadeOut');
      this.$(".my-content").one('webkitAnimationEnd', function() {
        this.remove();
      }.bind(this));
  },

  removeBtn: function (event) {
    event.preventDefault();
    this.$(".my-background").remove();
    this.$(".my-content").addClass('animated rollOut');
    this.$(".my-content").one('webkitAnimationEnd', function() {
      this.remove();
    }.bind(this));
  },

  render: function() {
    var content = this.template({ review: this.model });
    this.$el.html(content);
    this.onRender();
    return this;
  },

  onRender: function() {
    this.$('#star').raty('destroy');
    this.$('#star').raty({
      starOff: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-off_j7trzb.png",
      starOn: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-on_iezcg6.png",
      starHalf: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-half_w79ezb.png",
      helf: true,
      score: 0,
      scoreName:"review[grade]"
    });
  },

  postReview: function(event) {
    event.preventDefault();
    var formdata = $("#review-form").serializeJSON().review;
    formdata.grade *= 2;
    formdata.movie_id = this.movie.id;
    this.model.save(formdata, {
      success: function() {
        var per_votes = this.movie.get('votes');
        var updated_score = (this.movie.get('vote_score') * per_votes +
                            formdata.grade) / (per_votes + 1);
        this.movie.set('vote_score', updated_score);
        this.movie.set('votes', per_votes + 1);
        this.model.set('author_name', BDMI.CURRENT_USER.username);
        this.parentView.fromFetch = false;
        this.collection.add(this.model);
        this.$(".my-background").remove();
        this.$(".my-content").addClass('animated zoomOutUp');
        this.$(".my-content").one('webkitAnimationEnd', function() {
          this.remove();
        }.bind(this));
      }.bind(this),
      error: function(model,error,options) {
        var messages = $.parseJSON(error.responseText);
        this.flashAlert(messages.error);
      }.bind(this),
    });
  },

  // TODO save data for later
  saveForLater: function(event) {
    event.preventDefault();
    var formdata = $("#review-form").serializeJSON().review;
    this.model.set(formdata);
    this.$(".my-background").remove();
    this.$(".my-content").addClass('animated zoomOutDown');
    this.$(".my-content").one('webkitAnimationEnd', function() {
      this.remove();
    }.bind(this));
  },

  flashAlert: function(messages) {
    var alertView = new BDMI.Views.AlertView({
      messages: messages
    });
    $('body').append(alertView.$el);
    alertView.render();
    alertView.$(".alert").addClass('animated fadeIn');
    setTimeout(function() {
      alertView.$(".alert").removeClass('fadeIn');
      alertView.$(".alert").addClass('fadeOut');
      alertView.$(".alert").one("webkitAnimationEnd", function() {
        alertView.remove();
      });
    },2000);
  }
});
<file_sep>/app/views/api/movies/show.json.jbuilder
json.partial! 'movie_show',
              movie: @movie,
              watched: @watched,
              wanted: @wanted,
              score: @vote_score,
              votes: @votes,
              display_images: true,
              display_score: true,
              display_reviews: true,
              display_posters: true,
              display_actors: true,
              display_genres: true,
              display_user: true
<file_sep>/app/models/review.rb
# == Schema Information
#
# Table name: reviews
#
#  id         :integer          not null, primary key
#  author_id  :integer          not null
#  movie_id   :integer          not null
#  grade      :integer          not null
#  title      :string
#  body       :string
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Review < ActiveRecord::Base
  validates :author, :movie, :grade, :title, :body, presence:true

  belongs_to :movie
  belongs_to :author, foreign_key: :author_id, class_name: :User
  has_many :likes, as: :likeable
end
<file_sep>/app/models/genre.rb
# == Schema Information
#
# Table name: genres
#
#  id         :integer          not null, primary key
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Genre < ActiveRecord::Base
  validates :name, presence: true
  has_many :taggings
  has_many :movies, through: :taggings

  Tmdb::Api.key(ENV['THEMOVIEDB_API_KEY'])

  def self.search_genre_detail_by_id(id)
    Tmdb::Genre.detail(id)
  end

  def self.find_genre(genre)
    @genre = Genre.find_by(name: genre.capitalize) unless genre.nil?
    @genre
  end
end
<file_sep>/db/migrate/20150815172536_create_taggings.rb
class CreateTaggings < ActiveRecord::Migration
  def change
    create_table :taggings do |t|
      t.integer :movie_id, null: false
      t.integer :genre_id, null: false

      t.timestamps null: false
    end
    add_index :taggings, :movie_id
    add_index :taggings, :genre_id
  end
end
<file_sep>/app/views/api/actors/_actor.json.jbuilder
json.extract!(actor, :id, :name, :birthday)

if display_images
  json.images do
    json.array! actor.images do |image|
      json.partial! 'api/movies/image', image: image
    end
  end
end

if display_casting
  json.castings do
    json.array! actor.castings.where(movie_id: movie_id) do |casting|
      json.extract!(casting, :act_as, :ord)
    end
  end
end
<file_sep>/app/assets/javascripts/collections/movies.js
BDMI.Collections.Movies = Backbone.Collection.extend({
  url: "/api/movies",

  model: BDMI.Models.Movie,

  getOrFetch: function(id) {
    var movie = this.get(id);
    if (!movie) {
      movie = new BDMI.Models.Movie({ id:id });
      this.add(movie);
      var movies = this;
      movie.fetch({
        error: function() {
          movies.remove(movie);
        }
      });
    } else {
      movie.fetch();
    }
    return movie;
  }

});
<file_sep>/app/assets/javascripts/views/actors/actors.js
BDMI.Views.Actors = Backbone.CompositeView.extend({
  template: JST['actors/actors'],

  initialize: function() {
    this.listenTo(this.collection, 'add', this.addActorView);
    this.listenTo(this.collection, 'sync', this.render);
    this.collection.each(this.addActorView.bind(this));
  },

  addActorView: function(actor) {
    var subview = new BDMI.Views.Actor({ model: actor });
    this.addSubview(".all-actors",subview);
  },

  render: function() {
    var content = this.template();
    this.$el.html(content);
    this.attachSubviews();
    return this;
  }
});
<file_sep>/app/assets/javascripts/views/alert.js
BDMI.Views.AlertView = Backbone.View.extend({
  template: JST['alerts/no_more_alert'],

  className: "alert-view",

  initialize: function(options) {
    this.messages = options.messages;
  },

  render: function() {
    var messages = this.messages.join(", ");
    var content = this.template({ messages: messages });
    this.$el.html(content);
    return this;
  }
});
<file_sep>/app/assets/javascripts/models/intro_movie.js
BDMI.Models.IntroMovie = Backbone.Model.extend({
  urlRoot: "/api/intro_movies_index",

  parse: function(payload) {
    if(payload.images) {
      this.images().set(payload.images);
      delete payload.images;
    }
    if(payload.reviews) {
      this.reviews().set(payload.reviews);
      delete payload.reviews;
    }

    return payload;
  },

  images: function() {
    if(this._images === undefined) {
      this._images = new BDMI.Collections.MovieImages();
    }
    return this._images;
  },

  reviews: function() {
    if(this._reviews === undefined) {
      this._reviews = new BDMI.Collections.Reviews();
    }
    return this._reviews;
  }
});
<file_sep>/docs/phases/phase4.md
# Phase 4: Ranking System

## Rails
### Models
Movie

### Controllers
Api::MoviesController (search)

### Views
tags/<%=type%>.json.jbuilder

## Backbone
### Models
Type

### Collections
Types

### Views
User_show, User_reviews, Watch_list, Watched_list

## Gems/Libraries
<file_sep>/app/assets/javascripts/views/movies/first_result_item.js
BDMI.Views.FirstResultView = Backbone.CompositeView.extend({
  template: JST['movie/first_result_item'],

  className: "results_item",

  events: {
    // "click .first-item-image":"handleClick",
    "click #explore": "explore",
    // "mouseenter .first-item-image": "handleEnter",
    // "mouseleave .first-item-image": "handleLeave"
  },

  initialize: function() {
    this.listenTo(this.model, 'sync change', this.render);
  },

  render: function() {
    var poster_url = this.model.attributes.base_url + "original" +
    this.model.attributes.poster_path;
    var content = this.template({ movie: this.model, poster_url: poster_url });
    this.$el.html(content);
    this.onRender();
    return this;
  },

  handleClick: function(event) {
    this.$el.addClass('animated flipOutX');
    this.$el.one('webkitAnimationEnd', function() {
      this.movieShow();
      this.render();
    }.bind(this));
  },

  movieShow: function() {
    window.scrollTo(0, 0);
    Backbone.history.navigate("movies/"+this.model.id, { trigger: true });
  },

  handleEnter: function(event) {
    $(event.currentTarget).addClass('animated infinite pulse');
  },

  handleLeave: function(event) {
    $(event.currentTarget).removeClass('animated infinite pulse');
  },

  onRender: function() {
    this.$('#star').raty('destroy');
    var grade = this.model.attributes.vote_average / 2;
    this.$('#star').raty({
      starOff: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-off_j7trzb.png",
      starOn: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-on_iezcg6.png",
      starHalf: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-half_w79ezb.png",
      helf: true,
      score: grade,
      readOnly: true
    });
  },

  explore: function(event) {
    window.scrollTo(0, 0);
    Backbone.history.navigate("movies/"+this.model.id, { trigger: true });
  }
});
<file_sep>/README.md
# BDMI

Live: www.bdmi.co

## Screenshots
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694957/Screen_Shot_2015-09-07_at_11.43.19_PM_ucpox6.png)
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694957/Screen_Shot_2015-09-07_at_11.43.51_PM_r0z1hk.png)
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694957/Screen_Shot_2015-09-07_at_11.44.06_PM_yzw9rf.png)
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694957/Screen_Shot_2015-09-07_at_11.44.28_PM_rkmwzf.png)
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694957/Screen_Shot_2015-09-07_at_11.46.02_PM_geim40.png)
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694957/Screen_Shot_2015-09-07_at_11.44.42_PM_ghrtfx.png)
![alt tag](http://res.cloudinary.com/dypfv4yqq/image/upload/c_scale,w_400/v1441694958/Screen_Shot_2015-09-07_at_11.45.26_PM_y1yeil.png)


## Description

BDMI is a movie searching platform powered by Themoviedb.org. Users are able to search for movies by their titles in any languages, watch trailers, view the casts, and leave reviews. Users can also find the information that which movies are showing in theaters right now, or check the overall top rated movies. Each user has a watch-list that user can add the moives they want to watch later to it, and a watched movies list that collect all the movies that the user has watched.

## Technologies

Back-end:
- Single-page web app using Ruby on Rails and Backbone.js
- Uses custom script to seed Postgres SQL database with data through Tmdb's API
- Movie backdrops and actor/actress profile photos share an Images table via polymorphic associations to keep database         normailzed
- APIs used: Tmdb, Cloudinary

Front-end:
- Uses bootstrap and CSS3 animations
- Uses YouTube API to embed a YouTube player for playing trailers
- Uses scrollSpy


## Minimum Viable Product
BDMI is a clone of IMDB built on Rails and Backbone. Users can:

<!-- This is a Markdown checklist. Use it to keep track of your progress! -->

- [x] Create accounts
- [x] Create sessions (log in)
- [x] View movies info and grades
- [x] View casts
- [x] View reviews
- [x] Add reviews to movie
- [x] Grade movies
- [x] Play trailers
- [x] Search for movies by title
- [x] View actor/actress Info
- [x] View actor/actress's attended movies
- [x] Search for actor/actress by name
- [x] Add movie to personal watch list
- [x] Collect watched movies


## TO-DO List
- [ ] Add third parties login
- [ ] Allow user delete movies from list
- [ ] Allow user to reply to reviews
- [ ] Redesign user homepage for better user experience


<!--## Design Docs-->
<!--* [View Wireframes][views]-->
<!--* [DB schema][schema]-->

<!--[views]: ./docs/views.md-->
<!--[schema]: ./docs/schema.md-->

<!--## Implementation Timeline-->

<!--### Phase 1: User Authentication, Movie Index Page (~1 day)-->
<!--I will implement user authentication in Rails based on the practices learned at-->
<!--App Academy. By the end of this phase, users will be able to comment movies using-->
<!--a simple text form in a Rails view. The most important part of this phase will-->
<!--be pushing the app to Heroku and ensuring that everything works before moving on-->
<!--to phase 2.-->

<!--[Details][phase-one]-->

<!--### Phase 2: Viewing Movie Info and Adding Reviews (~4 days)-->
<!--I will add API routes to serve blog and post data as JSON, then add Backbone-->
<!--models and collections that fetch data from those routes. By the end of this-->
<!--phase, users will be able to view a list of movies and their grades, or view-->
<!--movies castings, add reviews, and grade the movie in its show page inside a-->
<!--single Backbone app.-->

<!--[Details][phase-two]-->

<!--### Phase 3: Search (~3 days)-->
<!--I'll need to add `search` routes to Movies and actors controllers. On the-->
<!--Backbone side, there will be a `SearchResults` composite view has `MoviesIndex`-->
<!--subviews and `ActorIndex` subviews.-->

<!--[Details][phase-three]-->

<!--### Phase 4: Play trailer and personal list(~2 days)-->
<!--I'll use YouTube api to embed YouTube player and play trailer in movie's show-->
<!--page. I'll have a user homepage which contains `UserReviews`, `UserWatchList`,-->
<!--and `UserWatchedList` subviews to display the information that the user stored-->
<!--in the app.-->

<!--[Details][phase-four]-->


<!--### Bonus Features (TBD)-->
<!--- [x] "Watched" button and "Would Watch" button-->
<!--- [x] Pagination/infinite scroll-->
<!--- [ ] Multiple sessions/session management-->
<!--- [ ] Typeahead search bar-->

<!--[phase-one]: ./docs/phases/phase1.md-->
<!--[phase-two]: ./docs/phases/phase2.md-->
<!--[phase-three]: ./docs/phases/phase3.md-->
<!--[phase-four]: ./docs/phases/phase4.md-->
<file_sep>/app/assets/javascripts/views/search_dropdown_item.js
BDMI.Views.SearchDropDownItem = Backbone.View.extend({
  template_movie: JST['movie/search_dropdown_item'],

  template_actor: JST['actors/search_dropdown_item'],

  className: 'search-dropdown-item',

  events: {
    'click': "handleClick"
  },

  initialize: function(options) {
    this.type = options.type;
  },

  render: function() {
    if (this.type === 'movie') {
      this.poster_url = this.model.attributes.base_url + "original" +
      this.model.attributes.poster_path;
      this.$el.html(this.template_movie({
        poster_url: this.poster_url,
        movie: this.model
      }));
      this.onRender();
    } else {
      var image_url = this.model.attributes.base_url + "original" +
      this.model.attributes.profile_path;
      var title = this.model.attributes.name;
      this.$el.html(this.template_actor({
        image_url: image_url,
        title: title
      }));
    }
    return this;
  },

  handleClick: function() {
    window.scrollTo(0, 0);
    if (this.type === 'movie') {
      Backbone.history.navigate("movies/"+this.model.id, { trigger: true });
    } else {
      Backbone.history.navigate('person/'+ this.model.id, { trigger: true });
    }
  },

  onRender: function() {
    this.$('#star').raty('destroy');
    var grade = this.model.attributes.vote_average / 2;
    this.$('#star').raty({
      starOff: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-off_j7trzb.png",
      starOn: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-on_iezcg6.png",
      starHalf: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-half_w79ezb.png",
      helf: true,
      score: grade,
      readOnly: true
    });
  },
});
<file_sep>/db/migrate/20150818233354_create_wantwatchmovies.rb
class CreateWantwatchmovies < ActiveRecord::Migration
  def change
    create_table :wantwatchmovies do |t|
      t.integer :user_id, null: false
      t.integer :movie_id, null: false

      t.timestamps null: false
    end
    add_index :wantwatchmovies, :user_id
    add_index :wantwatchmovies, :movie_id
  end
end
<file_sep>/app/views/api/users/_user_movie.json.jbuilder
json.extract!(
  movie,
  :id,
  :title,
  :release_date,
  :vote_average,
  :vote_count,
  :popularity,
  :revenue,
  :runtime,
  :overview,
  :budget,
  :tagline,
  :imdb_id,
)

json.set! :poster, movie.posters.first
<file_sep>/app/controllers/api/reviews_controller.rb
class Api::ReviewsController < ApplicationController
  def index
    page = params[:page] || 1
    movie_id = params[:movie_id]
    user_id = params[:user_id]
    if movie_id && user_id
      @reviews = Review.where('movie_id = ? and author_id = ?', movie_id, user_id)
                       .limit(4)
                       .offset(( page.to_i - 1) * 4 )
                       .includes(:author)
                       .order(created_at: :desc)
    elsif movie_id
      @reviews = Review.where('movie_id = ?', movie_id)
                       .limit(4)
                       .offset(( page.to_i - 1) * 4 )
                       .includes(:author)
                       .order(created_at: :desc)
    elsif user_id
      @reviews = Review.where('author_id = ?', user_id)
                       .limit(4)
                       .offset(( page.to_i - 1) * 4 )
                       .includes(:author)
                       .order(created_at: :desc)
    end
    render "api/reviews/index"
  end

  def create
    @review = current_user.reviews.new(review_params)
    if @review.save
      movie = Movie.find(@review.movie_id)
      movie.update_score_and_num_of_votes(@review.grade)
      movie.save
      render json: @review
    else
      render json: { error: @review.errors.full_messages }, status: 422
    end
  end

  def update
    @review = Review.find(params[:id])
    if @review.update(review_params)
      render json: @review
    else
      render json: { error: @review.errors.full_messages }, status: 422
    end
  end

  def destroy
    @review = Review.find(params[:id])
    @review.destroy
    render json: {}
  end

  def show
    @review = Review.find(params[:id])
    render json: @review
  end

  private

  # def current_movie
  #   @movie = Movie.find(params[:movie_id])
  # end

  def review_params
    params.require(:review).permit(:title, :body, :grade, :movie_id)
  end
end
<file_sep>/app/assets/javascripts/views/actors/actor_info.js
BDMI.Views.ActorInfoView = Backbone.CompositeView.extend({
  template: JST['actors/actor_info'],

  initialize: function() {
    this.listenTo(this.model, 'sync', this.render);
  },

  render: function() {
    var profile_url = this.model.images().first().attributes.image_url;
    var content = this.template({
      actor: this.model,
      profile_url: profile_url
     });
    this.$el.html(content);
    return this;
  }
});
<file_sep>/app/assets/javascripts/collections/watched_movies.js
BDMI.Collections.WatchedMovies = Backbone.Collection.extend({
  url: "/api/watcheds",
  model: BDMI.Models.WatchedMovie
});
<file_sep>/app/views/api/genres/_genre.json.jbuilder
json.extract!(genre, :name)
<file_sep>/app/views/api/actors/show.json.jbuilder
json.partial! 'actor_show',
              actor: @actor,
              credits: @credits,
              config: @config,
              display_images: true,
              display_movies: true
<file_sep>/app/models/movie.rb
# == Schema Information
#
# Table name: movies
#
#  id           :integer          not null, primary key
#  title        :string           not null
#  release_date :date
#  runtime      :integer
#  vote_average :float
#  vote_count   :integer
#  popularity   :float
#  overview     :text
#  imdb_id      :string
#  revenue      :integer
#  tagline      :string
#  budget       :integer
#  created_at   :datetime         not null
#  updated_at   :datetime         not null
#

require 'rest-client'

class Movie < ActiveRecord::Base
  validates :title, presence: true

  has_many :reviews
  has_many :images, as: :imageable
  has_many :likes, as: :likeable
  has_many :posters, dependent: :destroy
  has_many :castings
  has_many :actors, through: :castings
  has_many :taggings
  has_many :genres, through: :taggings
  has_one :trailer
  has_many :watch_wants, foreign_key: :movie_id, class_name: :Wantwatchmovie
  has_many :want_watchers, through: :watch_wants, source: :user
  has_many :watcheds, foreign_key: :movie_id, class_name: :Watchedmovie
  has_many :watchers, through: :watcheds, source: :user

  Tmdb::Api.key(ENV['THEMOVIEDB_API_KEY'])


  def update_score_and_num_of_votes(score)
    all_scores = self.vote_average * (self.vote_count + self.reviews.count - 1) + score
    score = all_scores / (self.vote_count + self.reviews.count)
  end

  def self.search_by_title(title)
    Tmdb::Movie.find(title)
  end

  def self.find_config
    Tmdb::Configuration.new
  end

  def self.search_and_store_by_id(id)
    auth = {
      cloud_name: ENV['CLOUD_NAME'],
      api_key: ENV['CLOUD_API_KEY'],
      api_secret: ENV['CLOUD_API_SECRET'],
      upload_preset: ENV['UPLOAD_PRESET']
    }
    config = Tmdb::Configuration.new
    movie = Tmdb::Movie.detail(id)
    id = movie['id']
    title = movie['title']
    release_date = movie['release_date']
    vote_average = movie['vote_average']
    vote_count = movie['vote_count']
    popularity = movie['popularity']
    overview = movie['overview']
    runtime = movie['runtime']
    imdb_id = movie['imdb_id']
    tagline = movie['tagline']
    budget = movie['budget']
    revenue = movie['revenue']

    unless movie['poster_path'].nil? || vote_count == 0 || release_date.nil?

      new_movie = Movie.create(
                  id: id,
                  title: title,
                  release_date: release_date,
                  vote_average: vote_average,
                  vote_count: vote_count,
                  popularity: popularity,
                  overview: overview,
                  runtime: runtime,
                  imdb_id: imdb_id,
                  tagline: tagline,
                  budget: budget,
                  revenue: revenue
      )

      movie['genres'].each do |genre|
        genre_id = genre['id']
        Tagging.create(genre_id: genre_id, movie_id: movie['id'])
      end

      unless movie['backdrop_path'].nil?
        backdrop_path = "#{config.base_url}original#{movie['backdrop_path']}"
        backdrop = Cloudinary::Uploader.upload(backdrop_path, auth)
        image_url = backdrop['url']
        new_movie.images.create(image_url: image_url)
      end

      poster_path = "#{config.base_url}original#{movie['poster_path']}"
      poster = Cloudinary::Uploader.upload(poster_path, auth)
      poster_url = poster['url']
      new_movie.posters.create(poster_url: poster_url)



      casts = Tmdb::Movie.casts(new_movie.id)
      casts.each do |actor|
        if actor['order'] < 11
          person = Tmdb::Person.detail(actor['id'])

          unless person['profile_path'].nil?

            if Actor.find_by(id: person['id']).nil?
              new_actor = Actor.create!(
                          id: person['id'],
                          name: person['name'],
                          place_of_birth: person['place_of_birth'],
                          birthday: person['birthday'],
                          biography: person['biography']
              )

              profile_path = "#{config.base_url}original#{person['profile_path']}"
              profile = Cloudinary::Uploader.upload(profile_path, auth)
              image_url = profile['url']

              new_actor.images.create!(image_url: image_url)
            end

            Casting.create!(
              actor_id: person['id'],
              movie_id: new_movie.id,
              ord: actor['order'],
              act_as: actor['character']
            )
          end
        end
      end
    end
    new_movie
  end

  def self.search_trailer_by_id(id)
    resp = Tmdb::Movie.trailers(id)
    trailer = resp["youtube"].first
    Trailer.create!(source: trailer["source"], movie_id:id) unless trailer.nil?
  end




#-------------------------------test---------------------------------
  def self.getData
    RestClient.get "http://api.themoviedb.org/3/movie/10195/reviews?api_key=#{ENV['THEMOVIEDB_API_KEY']}"
  end

  def self.getacData
    Tmdb::Person.detail(287)
  end


  def self.upload
    auth = {
      cloud_name: ENV['CLOUD_NAME'],
      api_key: ENV['CLOUD_API_KEY'],
      api_secret: ENV['CLOUD_API_SECRET'],
      upload_preset: ENV['UPLOAD_PRESET']
    };
     Cloudinary::Uploader.upload("http://cf2.imgobject.com/t/p/w500/8uO0gUM8aNqYLs1OsTBQiXu0fEv.jpg", auth)
  end


end
<file_sep>/test/models/movie_test.rb
# == Schema Information
#
# Table name: movies
#
#  id           :integer          not null, primary key
#  title        :string           not null
#  release_date :date
#  runtime      :integer
#  vote_average :float
#  vote_count   :integer
#  popularity   :float
#  overview     :text
#  imdb_id      :string
#  revenue      :integer
#  tagline      :string
#  budget       :integer
#  created_at   :datetime         not null
#  updated_at   :datetime         not null
#

require 'test_helper'

class MovieTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end
<file_sep>/app/models/trailer.rb
# == Schema Information
#
# Table name: trailers
#
#  id         :integer          not null, primary key
#  source     :string           not null
#  movie_id   :integer          not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Trailer < ActiveRecord::Base
  validates :source, :movie, presence: true
  validates :movie, uniqueness: true

  belongs_to :movie
end
<file_sep>/docs/schema.md
# Schema Information

## movies
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
title       | string    | not null
release date| date      |
vote_average| float     |
vote_count  | integer   |
popularity  | float     |
revenue     | integer   |
runtime     | integer   |
budget      | integer   |
tagline     | string    |
overview    | text      |
imdb_id     | string    |


## posters
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
movie_id    | integer   | not null, foreign key (references movies)
poster_url  | string    | not null


## castings
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
movie_id    | integer   | not null, foreign key (references movies)
actor_id    | integer   | not null, foreign key (references actors)
act_as      | string    |
ordering    | integer   |


## genres
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
name        | string    | not null


## images
column name   | data type | details
--------------|-----------|-----------------------
id            | integer   | not null, primary key
image_url     | string    | not null
thumbnil_url  | string    |
imageable_id  | integer   |
imageable_type| string    |


## trailers
column name    | data type | details
---------------|-----------|-----------------------
id             | integer   | not null, primary key
source         | string    | not null
movie_id       | integer   | not null, foreign key (references movies)


## actors
column name    | data type | details
---------------|-----------|-----------------------
id             | integer   | not null, primary key
name           | string    | not null
place_of_birth | string    |
birthday       | date      |
biography      | text      |

## reviews
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
author_id   | integer   | not null, foreign key (references users)
movie_id    | integer   | not null, foreign key (references movies)
title       | string    | not null
body        | string    |
grade       | integer   |

## taggings
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
movie_id    | integer   | not null, foreign key (references movies)
genre_id    | integer   | not null, foreign key (references genres)


## wantwatchmovies
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
movie_id    | integer   | not null, foreign key (references movies)
user_id     | integer   | not null, foreign key (references users)


## watchedmovies
column name | data type | details
------------|-----------|-----------------------
id          | integer   | not null, primary key
movie_id    | integer   | not null, foreign key (references movies)
user_id     | integer   | not null, foreign key (references users)


## users
column name     | data type | details
----------------|-----------|-----------------------
id              | integer   | not null, primary key
email           | string    | not null, unique
username        | string    | not null, unique
password_digest | string    | not null
session_token   | string    | not null, unique
<file_sep>/app/assets/javascripts/views/reviews/review.js
BDMI.Views.Review = Backbone.View.extend({
  template: JST['reviews/review'],

  className: "review-section-item",

  initialize: function() {
    this.listenTo(this.model, 'sync', this.render);
  },

  getTimeAgo: function(){
    return $.timeago(this.model.get('created_at'));
  },

  render: function() {
    var content = this.template({
      review: this.model,
      timeAgo: this.getTimeAgo()
    });
    this.$el.html(content);
    this.onRender();
    return this;
  },
  onRender: function() {
    this.$('#star').raty('destroy');
    var grade = this.model.attributes.grade / 2;
    this.$('#star').raty({
      starOff: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-off_j7trzb.png",
      starOn: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-on_iezcg6.png",
      starHalf: "http://res.cloudinary.com/dypfv4yqq/image/upload/v1439888579/star-half_w79ezb.png",
      helf: true,
      score: grade,
      readOnly: true
    });
  }
});
<file_sep>/docs/phases/phase1.md
# Phase 1: User Authentication, Basic Movies and Comments

## Rails
### Models
* User
* Movie
* Comment

### Controllers
* UsersController (create, new)
* SessionsController (create, new, destroy)
* Api::MoviesController (index, show)

### Views
* users/new.html.erb
* session/new.html.erb
* movies/index.json.jbuilder
* root.html.erb

## Backbone
### Models
* Movie

### Collections
* Movies

### Views
* Movies index

## Gems/Libraries
* jbuilder
* bcrypt
<file_sep>/app/assets/javascripts/views/search_dropdown.js
BDMI.Views.SearchDropDown = Backbone.CompositeView.extend({
  template: JST['movie/search_dropdown'],

  className: 'search_dropdown_section',

  initialize: function(options) {
    this.listenTo(this.collection, 'sync', this.render);
    this.listenTo(this.collection, 'add', this.addDropDownItem);
    this.collection.each(this.addDropDownItem.bind(this));
    this.count = 0;
    this.type = options.type;
  },

  addDropDownItem: function(model) {
    if(this.count < 5) {
      var subview = new BDMI.Views.SearchDropDownItem({
        model: model,
        type: this.type
      });
      this.addSubview('#dropdown-index', subview);
      this.count += 1;
    }
  },

  render: function() {
    var content = this.template();
    this.$el.html(content);
    this.attachSubviews();
    return this;
  }
});
<file_sep>/app/models/like.rb
# == Schema Information
#
# Table name: likes
#
#  id            :integer          not null, primary key
#  author_id     :integer          not null
#  likeable_id   :integer
#  likeable_type :string
#  created_at    :datetime         not null
#  updated_at    :datetime         not null
#

class Like < ActiveRecord::Base
  validates :author, presence: true
  belongs_to :author, foreign_key: :author_id, class_name: :User
  belongs_to :likeable, polymorphic: true
end
<file_sep>/app/models/actor.rb
# == Schema Information
#
# Table name: actors
#
#  id             :integer          not null, primary key
#  name           :string           not null
#  place_of_birth :string
#  birthday       :date
#  created_at     :datetime         not null
#  updated_at     :datetime         not null
#  biography      :text
#

class Actor < ActiveRecord::Base
  validates :name, presence: true

  has_many :castings
  has_many :movies, through: :castings
  has_many :images, as: :imageable
  has_many :likes, as: :likeable

  Tmdb::Api.key(ENV['THEMOVIEDB_API_KEY'])

  def self.search_actors_by_name(name)
    Tmdb::Person.find(name)
  end

  def self.search_credits(id)
    Tmdb::Person.credits(id)
  end

  def self.search_and_store_by_id(id)
    auth = {
      cloud_name: ENV['CLOUD_NAME'],
      api_key: ENV['CLOUD_API_KEY'],
      api_secret: ENV['CLOUD_API_SECRET'],
      upload_preset: ENV['UPLOAD_PRESET']
    }
    config = Tmdb::Configuration.new

    person = Tmdb::Person.detail(id)

    actor = Actor.find_by(id: person['id'])
    if actor.nil?
      new_actor = Actor.create!(
                  id: person['id'],
                  name: person['name'],
                  place_of_birth: person['place_of_birth'],
                  birthday: person['birthday'],
                  biography: person['biography']
      )

      profile_path = "#{config.base_url}original#{person['profile_path']}"
      profile = Cloudinary::Uploader.upload(profile_path, auth)
      image_url = profile['url']

      new_actor.images.create!(image_url: image_url)
    else
      actor.update(biography: person['biography'])
    end
  end
end
<file_sep>/app/views/api/users/show.json.jbuilder
json.extract! @user, :username

json.reviews do
  json.array! @user.reviews.includes(:movie, :author) do |review|
    json.partial! 'user_review', review: review, author_name: @user.username
  end
end

json.watched_movies do
  json.array! @user.watched_movies.includes(:posters) do |movie|
    json.partial! 'user_movie', movie: movie
  end
end

json.wanted_movies do
  json.array! @user.wanted_movies.includes(:posters) do |movie|
    json.partial! 'user_movie', movie: movie
  end
end
<file_sep>/app/models/casting.rb
# == Schema Information
#
# Table name: castings
#
#  id         :integer          not null, primary key
#  movie_id   :integer
#  actor_id   :integer
#  ord        :integer
#  act_as     :string
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Casting < ActiveRecord::Base
  belongs_to :actor
  belongs_to :movie
end
<file_sep>/app/models/user.rb
# == Schema Information
#
# Table name: users
#
#  id              :integer          not null, primary key
#  email           :string           not null
#  username        :string           not null
#  password_digest :string           not null
#  session_token   :string           not null
#  created_at      :datetime         not null
#  updated_at      :datetime         not null
#

class User < ActiveRecord::Base
  validates :email, :username, :password_digest, :session_token, presence: true
  validates :email, :username, :session_token, uniqueness: true
  validates :password, length: { minimum: 6, allow_nil: true}

  attr_reader :password

  has_many :images, as: :imageable
  has_many :reviews, foreign_key: :author_id, class_name: :Review
  has_many :likes, foreign_key: :author_id, class_name: :Like
  has_many :want_watchs, foreign_key: :user_id, class_name: :Wantwatchmovie
  has_many :wanted_movies, through: :want_watchs, source: :movie
  has_many :has_watcheds, foreign_key: :user_id, class_name: :Watchedmovie
  has_many :watched_movies, through: :has_watcheds, source: :movie

  after_initialize :ensure_session_token

  def password=(password)
    @password = <PASSWORD>
    self.password_digest = BCrypt::Password.create(password)
  end

  def is_password?(password)
    BCrypt::Password.new(self.password_digest).is_password?(password)
  end

  def self.find_by_credentials(emailOrUsername,password)
    user = User.find_by(email: emailOrUsername)
    user ||= User.find_by(username: emailOrUsername)
    return nil unless user && user.is_password?(password)
    user
  end

  def reset_token!
    self.session_token = SecureRandom.urlsafe_base64(16)
    self.save!
    self.session_token
  end

  private

  def ensure_session_token
    self.session_token ||= SecureRandom.urlsafe_base64(16)
  end
end
<file_sep>/app/assets/javascripts/models/in_theaters_movie.js
BDMI.Models.InTheatersMovie = Backbone.Model.extend({
  urlRoot: "/api/in_theaters_movies",

  parse: function(payload) {
    if(payload.images) {
      this.images().set(payload.images);
      delete payload.images;
    }
    if(payload.reviews) {
      this.reviews().set(payload.reviews);
      delete payload.reviews;
    }
    if(payload.posters) {
      this.posters().set(payload.posters);
      delete payload.posters;
    }

    return payload;
  },

  images: function() {
    if(this._images === undefined) {
      this._images = new BDMI.Collections.MovieImages();
    }
    return this._images;
  },

  reviews: function() {
    if(this._reviews === undefined) {
      this._reviews = new BDMI.Collections.Reviews();
    }
    return this._reviews;
  },

  posters: function() {
    if(this._posters === undefined) {
      this._posters = new BDMI.Collections.MoviePosters();
    }
    return this._posters;
  }
});
<file_sep>/app/controllers/api/genres_controller.rb
class Api::GenresController < ApplicationController
  def index
    @genre = Genre.find_genre(params[:genre]) || Genre.first
    @config = Movie.find_config
    page = params[:page].to_i
    @genres = Genre.all
    @movies = Genre.search_genre_detail_by_id(@genre.id)
    @movies = @movies.get_page(page) unless (0..1).include? page
    render 'genre_index'
  end

  def show
  end
end
<file_sep>/app/assets/javascripts/collections/in_theaters_movies.js
BDMI.Collections.InTheatersMovies = Backbone.Collection.extend({
  url: "/api/in_theaters_movies",
  model: BDMI.Models.InTheatersMovie
});
<file_sep>/app/controllers/api/movies_controller.rb
class Api::MoviesController < ApplicationController

  def show
    @movie = Movie.find_by(id: params[:id])
    unless @movie
      @movie = Movie.search_and_store_by_id(params[:id])
    end
    @watched =  @movie.watchers.include?(current_user)
    @wanted = @movie.want_watchers.include?(current_user)
    @reviews = @movie.reviews
    @votes = @movie.vote_count + @movie.reviews.count
    @vote_score = (@movie.vote_average * @movie.vote_count +
                  @reviews.sum(:grade) + @reviews.count) / @votes
    render 'show'
  end

  def intro_movies_index
    @movies = Movie.order('popularity DESC').limit(4)
    render 'movie_index'
  end

  def in_theaters_movies_index
    page = params[:page] || 1
    @movies = Movie.where('release_date > ? and release_date < ?',
                          3.months.ago, Date.today).order("vote_count DESC")
                          .limit(8).offset((page.to_i  - 1) * 8)
    render 'movie_index'
  end

  def top_rated
    page = params[:page] || 1
    @movies = Movie.where('vote_average > ? and vote_count > ?',
                          7.5, 10).order("vote_average DESC")
                          .limit(8).offset((page.to_i  - 1) * 8)
    render 'movie_index'
  end

  def search
    title = params["title"] || "Spider man"
    @movies = Movie.search_by_title(title)
    @config = Movie.find_config
    render 'search'
  end

  def search_trailer
    @movie = Movie.find(params[:movie_id])
    unless @movie.trailer
      trailer = Movie.search_trailer_by_id(@movie.id)
      render json: trailer
    else
      render json: @movie.trailer
    end
  end
end
<file_sep>/app/assets/javascripts/models/wanted_movie.js
BDMI.Models.WantedMovie = Backbone.Model.extend({
  urlRoot: "/api/wanteds"
});
<file_sep>/app/models/poster.rb
# == Schema Information
#
# Table name: posters
#
#  id         :integer          not null, primary key
#  poster_url :string           not null
#  movie_id   :integer          not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class Poster < ActiveRecord::Base
  validates :poster_url, :movie, presence: true
  belongs_to :movie
end
<file_sep>/app/assets/javascripts/views/users/user_watched_list.js
BDMI.Views.UserWatchedList = Backbone.CompositeView.extend({
  template: JST['users/user_watched_list'],

  className: 'group',

  initialize: function() {
    this.listenTo(this.collection, 'sync', this.render);
    this.listenTo(this.collection, 'add', this.addWatchedItem);
    this.collection.each(this.addWatchedItem.bind(this));
  },

  addWatchedItem: function(movie) {
    var subview = new BDMI.Views.InTheatersItem({ model: movie });
    this.addSubview('.all-watched', subview);
  },

  render: function() {
    var content = this.template();
    if (this.collection.length === 0) {
      content = "<h4 class='no-review user-no'>You don't have any watched movies.</h4>";
    }
    this.$el.html(content);
    this.attachSubviews();
    return this;
  }
});
<file_sep>/app/assets/javascripts/views/genres/genres_index.js
BDMI.Views.GenresIndex = Backbone.CompositeView.extend({
  template: JST['genres/genres_index'],

  events: {
    "click #sort_by_date":"sort_by_date",
    "click #sort_by_rating":"sort_by_rating",
    "click #sort_by_count":"sort_by_count",
    "click .btn": "handleRedirect"
  },

  initialize: function(options) {
    this.firstItem = true;
    this.genres = [];
    this.addLoadingView();
    this.addPageScrollAnimation();
    this.genre = options.genre;
    if(this.genre === "all") {
      this.notChoose = true;
    } else {
      this.notChoose = false;
    }
    this.collection.fetch({
      data:{
        genre: this.genre
      },
      processData:true,
      success: function(collection) {
        this.genres = this.collection.first().attributes.genres;
      }.bind(this),
      error: function(collection, error) {
      }
    });
    this.listenTo(this.collection, 'sync', this.render);
  },

  render: function() {
    if (this.notChoose) {
      var content = this.template({
        genres: this.genres,
        notChoose: this.notChoose
      })
      this.$el.html(content);
      $(".wrap_body").remove();
    } else {
      var content = this.template({
        keyword: this.genre,
        section: "genres",
        genres : this.genres,
        notChoose: this.notChoose
      });
      this.$el.html(content);
      if (_.size(this.collection) !== 0) {
        var filtered = _.filter(this.collection.models, function(model) {
          return model.escape("release_date") !== "" &&
                 model.escape("vote_count") > 2;
        });
        this.collection = new BDMI.Collections.Genres(filtered);
        this.collection.each(this.addResultView.bind(this));
        this.generatePagination();
        this.addButtonActive();
        $(".wrap_body").remove();
      }
      this.attachSubviews();
    }
    return this;
  },

  handleRedirect: function(event) {

  },

  addResultView: function(result) {
    if (this.firstItem) {
      var firstSubview = new BDMI.Views.FirstResultView({ model: result });
      this.addSubview("#results-section", firstSubview);
      this.firstItem = false;
    } else {
      var subview = new BDMI.Views.ResultsItem({ model: result });
      this.addSubview("#results-section",subview);
    }
  },

  insertModelToSubviews: function(reverse) {
    var modelArr = _.toArray(this.collection.models);
    if (reverse) {
      modelArr = modelArr.reverse();
    }
    var subviews = _.toArray(this.subviews("#results-section")._wrapped);
    for (var i = 0; i < subviews.length && i < modelArr.length; i++) {
      subviews[i].model = modelArr[i];
      subviews[i].render();
    }
  },

  generatePagination: function() {
    $('#pagination-genres').twbsPagination({
        totalPages: 96,
        visiblePages: 8,
        onPageClick: function (event, page) {
          window.scrollTo(0, 0);
          this.collection.fetch({
            data: {
              page:page
            },
            processData:true,
            success: function() {
              this.insertModelToSubviews(false);
              $(".wrap_body").remove();
            }.bind(this)
          });
          this.addLoadingView();
        }.bind(this)
    });
  },

  sort_by_date: function(event) {
    this.$("#sort-section .btn").removeClass('active');
    $(event.currentTarget).addClass('active');
    this.collection.comparator = "release_date";
    this.collection.sort();
    this.insertModelToSubviews(true);
  },

  sort_by_count: function(event) {
    this.$("#sort-section .btn").removeClass('active');
    $(event.currentTarget).addClass('active');
    this.collection.comparator = "vote_count";
    this.collection.sort();
    this.insertModelToSubviews(true);
  },

  sort_by_rating: function(event) {
    this.$("#sort-section .btn").removeClass('active');
    $(event.currentTarget).addClass('active');
    this.collection.comparator = "vote_average";
    this.collection.sort();
    this.insertModelToSubviews(true);
  },

  addLoadingView: function() {
    this.loadingView = new BDMI.Views.LoadingView();
    $("body").append(this.loadingView.render().$el);
  },

  addButtonActive: function() {
    var currentGenre = Backbone.history.fragment.slice(7);
    if (currentGenre === "Science Fiction") {
      this.$(".Science.Fiction").addClass('active');
    } else if (currentGenre === "TV Movie") {
      this.$(".TV.Movie").addClass('active');
    } else {
      this.$("#all-genres-section .active").removeClass('active');
      this.$("#all-genres-section ." + currentGenre).addClass('active');
    }
  },

  addPageScrollAnimation: function() {
    $('a.page-scroll').bind('click', function(event) {
        if(Backbone.history.getFragment() !== "" &&
          !$(event.currentTarget).hasClass('user_login')) {
          Backbone.history.navigate("", { trigger: true });
        }
        var $anchor = $(this);
        if ($($anchor.attr('href')) !== []) {
          $('html, body').stop().animate({
              scrollTop: $($anchor.attr('href')).offset().top
          }, 1500, 'easeInOutExpo');
        }
        event.preventDefault();
    });
  }
});
<file_sep>/app/assets/javascripts/routers/router.js
BDMI.Routers.Router = Backbone.Router.extend({
  routes: {
    "": "index",
    "movies/:id": "movieShow",
    "person/:id": "personShow",
    "search/movies/:title": "movieIndex",
    "search/person/:name": "personIndex",
    "genres/:genre":"genresIndex",
    "users/:id": "userShow"
  },

  initialize: function(options) {
    this.$rootEl = options.$rootEl;
    this.movies = new BDMI.Collections.Movies();
    this.person = new BDMI.Collections.MovieActors();
  },

  index: function() {
    var mainView = new BDMI.Views.Main();
    this.swap(mainView);
  },

  movieShow: function(id) {
    var movie = this.movies.getOrFetch(id);
    var showView = new BDMI.Views.Movie({ model: movie });
    this.swap(showView);
  },

  userShow: function(id) {
    var user = new BDMI.Models.User({id: id});
    user.fetch({
      success: function() {
        var userShowView = new BDMI.Views.UserShow({ model: user });
        this.swap(userShowView);
      }.bind(this)
    });
  },

  movieIndex: function(title) {
    var searchedMovies = new BDMI.Collections.SearchedMovies();
    var searchedMovie = searchedMovies.fetch({
      data: {
        title:title
      },
      processData: true,
      success: function(collection) {
        if (collection.length === 0) {
          $(".wrap_body").remove();
          this.flashAlert(['No results for "'+ title +'"... ']);
        }
      }.bind(this)
    });
    var resultView = new BDMI.Views.ResultView({
      collection: searchedMovies,
      keyword:title,
      section: "movie"
    });
    this.swap(resultView);
  },

  personShow: function(id) {
    var person = this.person.getOrFetch(id);
    var showView = new BDMI.Views.ActorShow({ model: person });
    this.swap(showView);
  },

  personIndex: function(name) {
    var searchedPerson = new BDMI.Collections.MovieActors();
    var person = searchedPerson.fetch({
      data: {
        name: name
      },
      processData: true,
      success: function(collection) {
        $(".wrap_body").remove();
        if (collection.length === 0) {
          this.flashAlert(['No results for "'+ name +'"... ']);
        }
      }.bind(this)
    });
    var resultView = new BDMI.Views.ActorResult({
      collection: searchedPerson,
      keyword: name,
      section: "person"
    });
    this.swap(resultView);
  },

  genresIndex: function(genre) {
    var genres = new BDMI.Collections.Genres();
    var genresIndexView = new BDMI.Views.GenresIndex({
      collection: genres,
      genre: genre
    });
    this.swap(genresIndexView);
  },

  swap: function(view) {
    this._view && this._view.remove();
    this._view = view;
    this.$rootEl.html(view.render().$el);
  },

  flashAlert: function(messages) {
    var alertView = new BDMI.Views.AlertView({
      messages: messages
    });
    $('body').append(alertView.$el);
    alertView.render();
    alertView.$(".alert").addClass('animated fadeIn');
    setTimeout(function() {
      alertView.$(".alert").removeClass('fadeIn');
      alertView.$(".alert").addClass('fadeOut');
      alertView.$(".alert").one("webkitAnimationEnd", function() {
        alertView.remove();
        window.scrollTo(0, 0);
        Backbone.history.navigate("", { trigger: true });
      });
    },3000);
  }
});
