[
  {
    "path": ".circleci/config.yml",
    "content": "# Ruby CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-ruby/ for more details\n#\nversion: 2\njobs:\n  build:\n    docker:\n      # specify the version you desire here\n       - image: circleci/ruby:2.4.1-node-browsers\n      \n      # Specify service dependencies here if necessary\n      # CircleCI maintains a library of pre-built images\n      # documented at https://circleci.com/docs/2.0/circleci-images/\n      # - image: circleci/postgres:9.4\n\n    working_directory: ~/repo\n\n    steps:\n      - checkout\n\n      # Download and cache dependencies\n      - restore_cache:\n          keys:\n          - v1-dependencies-{{ checksum \"Gemfile.lock\" }}\n          # fallback to using the latest cache if no exact match is found\n          - v1-dependencies-\n\n      - run:\n          name: install dependencies\n          command: |\n            bundle install --jobs=4 --retry=3 --path vendor/bundle\n\n      - save_cache:\n          paths:\n            - ./vendor/bundle\n          key: v1-dependencies-{{ checksum \"Gemfile.lock\" }}\n        \n      # Database setup\n      - run: bundle exec rake db:create\n      - run: bundle exec rake db:schema:load\n\n      # run tests!\n      - run:\n          name: run tests\n          command: |\n            mkdir /tmp/test-results\n            TEST_FILES=\"$(circleci tests glob \"spec/**/*_spec.rb\" | circleci tests split --split-by=timings)\"\n            \n            bundle exec rspec --format progress \\\n                            --format RspecJunitFormatter \\\n                            --out /tmp/test-results/rspec.xml \\\n                            --format progress \\\n                            $TEST_FILES\n\n      # collect reports\n      - store_test_results:\n          path: /tmp/test-results\n      - store_artifacts:\n          path: /tmp/test-results\n          destination: test-results\n"
  },
  {
    "path": ".document",
    "content": "lib/**/*.rb\nbin/*\n- \nfeatures/**/*.feature\nLICENSE.txt\n"
  },
  {
    "path": ".gitignore",
    "content": "Gemfile.lock\n\n# rdoc generated\nrdoc\n\n# yard generated\ndoc\n.yardoc\n\n# bundler\n.bundle\n\n# jeweler generated\npkg\n\n# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: \n#\n# * Create a file at ~/.gitignore\n# * Include files you want ignored\n# * Run: git config --global core.excludesfile ~/.gitignore\n#\n# After doing this, these files will be ignored in all your git projects,\n# saving you from having to 'pollute' every project you touch with them\n#\n# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)\n#\n# For MacOS:\n#\n#.DS_Store\n\n# For TextMate\n#*.tmproj\n#tmtags\n\n# For emacs:\n#*~\n#\\#*\n#.\\#*\n\n# For vim:\n#*.swp\n\n# For redcar:\n#.redcar\n\n# For rubinius:\n#*.rbc\n"
  },
  {
    "path": "Gemfile",
    "content": "source \"http://rubygems.org\"\n\ngem \"rails\", \">= 3.0.7\"\n\ngroup :development do\n  gem \"shoulda\", \"~> 3.0.0.beta2\"\n  gem \"bundler\", \"~> 1.15.4\"\n  gem \"jeweler\", \"~> 1.6.2\"\n  gem \"turn\"\n  gem \"sqlite3\"\n  gem \"rdoc\"\nend\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2011 Diógenes Falcão\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.rdoc",
    "content": "= coletivo\n\nA simple Rails 3 recommendations engine.\nColetivo uses {Euclidean Distance}[http://en.wikipedia.org/wiki/Euclidean_distance] or {Pearson's Correlation Coefficient}[http://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient] to calculate the similarity between persons and their preferences.\n\n== Installation:\n\n  sudo gem install coletivo\n  rails g coletivo\n  rake db:migrate\n\n== Usage:\n\nAt your Rails model that represents a person (can be an _User_, _Member_, or something like that):\n\n  class User < ActiveRecord::Base\n    has_own_preferences\n\n    # ...\n  end\n\nSo, a person can rate things:\n\n  current_user = User.create(:name => 'Diogenes')\n  movie = Movie.create(:name => 'The Tourist', :year => 2010)\n\n  current_user.rate!(movie, 4.5)\n\nAnd after a lot of ratings... *recommendations*:\n\n  Movie.find_recommendations_for(current_user) # => movies and more movies...\n\nBy default, the similarity strategy used is Euclidean Distance, but you can change that passing the _strategy_ option:\n  Movie.find_recommendations_for(current_user, :strategy => :pearson)\n\n\n== Contributing to coletivo\n \n* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet\n* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it\n* Fork the project\n* Start a feature/bugfix branch\n* Commit and push until you are happy with your contribution\n* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.\n* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.\n\n== Copyright\n\nCopyright (c) 2011 Diógenes Falcão. See LICENSE.txt for\nfurther details.\n"
  },
  {
    "path": "Rakefile",
    "content": "# encoding: utf-8\n\nrequire 'rubygems'\nrequire 'bundler'\nbegin\n  Bundler.setup(:default, :development)\nrescue Bundler::BundlerError => e\n  $stderr.puts e.message\n  $stderr.puts \"Run `bundle install` to install missing gems\"\n  exit e.status_code\nend\nrequire 'rake'\n\nrequire 'jeweler'\nJeweler::Tasks.new do |gem|\n  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options\n  gem.name = \"coletivo\"\n  gem.homepage = \"http://github.com/diogenes/coletivo\"\n  gem.license = \"MIT\"\n  gem.summary = %Q{A simple Rails 3 recommendations engine}\n  gem.description = %Q{A simple Rails 3 recommendations engine}\n  gem.email = \"diogenes.araujo@gmail.com\"\n  gem.authors = [\"Diógenes Falcão\"]\n  gem.files = Dir[\"{lib}/**/*\"]\n  # dependencies defined in Gemfile\nend\nJeweler::RubygemsDotOrgTasks.new\n\nrequire 'rake/testtask'\nRake::TestTask.new(:test) do |test|\n  test.libs << 'lib' << 'test'\n  test.pattern = 'test/**/*_test.rb'\n  test.verbose = true\nend\n\ntask :default => :test\n\nrequire 'rake/rdoctask'\nRake::RDocTask.new do |rdoc|\n  version = File.exist?('VERSION') ? File.read('VERSION') : \"\"\n\n  rdoc.rdoc_dir = 'rdoc'\n  rdoc.title = \"coletivo #{version}\"\n  rdoc.rdoc_files.include('README*')\n  rdoc.rdoc_files.include('lib/**/*.rb')\nend\n"
  },
  {
    "path": "VERSION",
    "content": "0.0.3"
  },
  {
    "path": "coletivo.gemspec",
    "content": "# Generated by jeweler\n# DO NOT EDIT THIS FILE DIRECTLY\n# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'\n# -*- encoding: utf-8 -*-\n\nGem::Specification.new do |s|\n  s.name = %q{coletivo}\n  s.version = \"0.0.3\"\n\n  s.required_rubygems_version = Gem::Requirement.new(\">= 0\") if s.respond_to? :required_rubygems_version=\n  s.authors = [\"Di\\303\\263genes Falc\\303\\243o\"]\n  s.date = %q{2011-10-31}\n  s.description = %q{A simple Rails 3 recommendations engine}\n  s.email = %q{diogenes.araujo@gmail.com}\n  s.extra_rdoc_files = [\n    \"LICENSE.txt\",\n    \"README.rdoc\"\n  ]\n  s.files = [\n    \"lib/coletivo.rb\",\n    \"lib/coletivo/models/person.rb\",\n    \"lib/coletivo/models/person_rating.rb\",\n    \"lib/coletivo/models/recommendable.rb\",\n    \"lib/coletivo/rails/active_record.rb\",\n    \"lib/coletivo/rails/engine.rb\",\n    \"lib/coletivo/similarity/base_strategy.rb\",\n    \"lib/coletivo/similarity/engine.rb\",\n    \"lib/coletivo/similarity/euclidean_distance_strategy.rb\",\n    \"lib/coletivo/similarity/pearson_correlation_strategy.rb\",\n    \"lib/generators/coletivo/coletivo_generator.rb\",\n    \"lib/generators/coletivo/templates/person_ratings_migration.rb\"\n  ]\n  s.homepage = %q{http://github.com/diogenes/coletivo}\n  s.licenses = [\"MIT\"]\n  s.require_paths = [\"lib\"]\n  s.rubygems_version = %q{1.3.7}\n  s.summary = %q{A simple Rails 3 recommendations engine}\n\n  if s.respond_to? :specification_version then\n    current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION\n    s.specification_version = 3\n\n    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then\n      s.add_runtime_dependency(%q<rails>, [\">= 3.0.7\"])\n      s.add_development_dependency(%q<shoulda>, [\"~> 3.0.0.beta2\"])\n      s.add_development_dependency(%q<bundler>, [\"~> 1.0.14\"])\n      s.add_development_dependency(%q<jeweler>, [\"~> 1.6.2\"])\n      s.add_development_dependency(%q<turn>, [\">= 0\"])\n      s.add_development_dependency(%q<sqlite3>, [\">= 0\"])\n    else\n      s.add_dependency(%q<rails>, [\">= 3.0.7\"])\n      s.add_dependency(%q<shoulda>, [\"~> 3.0.0.beta2\"])\n      s.add_dependency(%q<bundler>, [\"~> 1.0.14\"])\n      s.add_dependency(%q<jeweler>, [\"~> 1.6.2\"])\n      s.add_dependency(%q<turn>, [\">= 0\"])\n      s.add_dependency(%q<sqlite3>, [\">= 0\"])\n    end\n  else\n    s.add_dependency(%q<rails>, [\">= 3.0.7\"])\n    s.add_dependency(%q<shoulda>, [\"~> 3.0.0.beta2\"])\n    s.add_dependency(%q<bundler>, [\"~> 1.0.14\"])\n    s.add_dependency(%q<jeweler>, [\"~> 1.6.2\"])\n    s.add_dependency(%q<turn>, [\">= 0\"])\n    s.add_dependency(%q<sqlite3>, [\">= 0\"])\n  end\nend\n\n"
  },
  {
    "path": "lib/coletivo/models/person.rb",
    "content": "module Coletivo\n  module Models\n    module Person\n      def self.included(base)\n        base.extend ClassMethods\n      end\n\n      module ClassMethods\n        # TODO: has_own_preferences doc.\n        def has_own_preferences(options = {})\n          self.send :include, InstanceMethods\n        end\n      end # ClassMethods\n\n      module InstanceMethods\n        def rate!(rateable, weight)\n          Coletivo::Config.ratings_container.create!({\n            :person => self,\n            :rateable => rateable,\n            :weight => weight\n          })\n        end\n      end # InstanceMethods\n\n    end # Person\n  end # Models\nend\n"
  },
  {
    "path": "lib/coletivo/models/person_rating.rb",
    "content": "module Coletivo\n  module Models\n    class PersonRating < ActiveRecord::Base\n      belongs_to :person, :polymorphic => true\n      belongs_to :rateable, :polymorphic => true\n\n      validates :person, :rateable, :weight, :presence => true\n\n      def self.find_for_recommendation(person, rateable_type)\n        where(:rateable_type => rateable_type.to_s)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/coletivo/models/recommendable.rb",
    "content": "module Coletivo\n  module Models\n    module Recommendable\n      def self.included(base)\n        base.extend ClassMethods\n        base.send :include, InstanceMethods\n      end\n\n      module ClassMethods\n        def find_recommendations_for(person, options = {})\n          preferences = options[:preferences] ||=\n            load_preferences_for_recommendation(person)\n          top = predict_highest_ratings(person, preferences, options)\n          ids = top.collect(&:last)\n\n          where(:id => ids).limit(options[:limit]).all\n        end\n\n        def map_ratings_to_preferences(ratings)\n          #TODO: (???) Item based mapping.\n          key, subkey = :person_id, :rateable_id\n          preferences = {}\n\n          ratings.each do |rating|\n            p = preferences[rating.send(key)] ||= {}\n            p[rating.send(subkey)] = rating.weight\n          end\n\n          preferences\n        end\n\n        def load_preferences_for_recommendation(person)\n          r = Coletivo::Config.ratings_container\\\n                .find_for_recommendation(person, self)\n\n          map_ratings_to_preferences(r)\n        end\n\n        private\n\n        def predict_highest_ratings(person, people_preferences, options)\n          data = {}\n          people_preferences.each do |other, other_prefs|\n            next if other == person\n\n            sim = person.similarity_with(other, options)\n            next if sim <= 0\n\n            other_prefs.each do |item, weight|\n              unless people_preferences[person.id].keys.include?(item)\n                data[item] ||= {:total_similarity => 0.0, :weighted_mean => 0.0}\n                data[item][:total_similarity] += sim\n                data[item][:weighted_mean] += weight * sim\n              end\n            end\n          end\n\n          # e.g: [[5.35, \"movie_2\"], [2.0, \"movie_4\"]]\n          guessed_rating_and_id = Proc.new do |item, item_data|\n            [item_data[:weighted_mean] / item_data[:total_similarity], item]\n          end\n\n          # DESC sorting by weighted mean of ratings\n          data.collect(&guessed_rating_and_id).sort_by(&:first).reverse\n        end\n      end\n\n      module InstanceMethods\n        def similarity_with(other_id, options = {})\n          p = options[:preferences] ||\n            self.class.load_preferences_for_recommendation(self)\n\n          Coletivo::Similarity::Engine\\\n            .similarity_between(self.id, other_id, p, options)\n        end\n      end\n    end # Recommendable\n  end # Models\nend\n"
  },
  {
    "path": "lib/coletivo/rails/active_record.rb",
    "content": "ActiveRecord::Base.send :include, Coletivo::Models::Person\nActiveRecord::Base.send :include, Coletivo::Models::Recommendable\n"
  },
  {
    "path": "lib/coletivo/rails/engine.rb",
    "content": "require 'coletivo'\n\nmodule Coletivo\n  class Engine < Rails::Engine\n  end\nend\n"
  },
  {
    "path": "lib/coletivo/similarity/base_strategy.rb",
    "content": "module Coletivo\n  module Similarity\n    class BaseStrategy\n      attr_accessor :preferences\n\n      def similarity_between(one, other)\n        raise \"The #similarity_between was not implemented in #{self.class}\"\n      end\n\n      def train_with(people_preferences)\n        @preferences = people_preferences\n      end\n\n      protected\n\n      def shared_items_between(one, other)\n        return [] unless preferences[one] && preferences[other]\n\n        preferences[one].keys.select { |item|\n          preferences[other].keys.include? item\n        }\n      end\n    end\n  end # Similarity\nend # Coletivo\n"
  },
  {
    "path": "lib/coletivo/similarity/engine.rb",
    "content": "module Coletivo\n  module Similarity\n    class Engine\n      def self.similarity_between(one, other, preferences, options = {})\n        strategy = load_strategy options[:strategy]\n        strategy.train_with(preferences)\n\n        strategy.similarity_between(one, other)\n      end\n\n      protected\n\n      def self.load_strategy(key)\n        if :pearson == key\n          Coletivo::Similarity::PearsonCorrelationStrategy.new\n        else\n          Coletivo::Similarity::EuclideanDistanceStrategy.new\n        end\n      end\n    end # Engine\n  end # Similarity\nend\n"
  },
  {
    "path": "lib/coletivo/similarity/euclidean_distance_strategy.rb",
    "content": "module Coletivo\n  module Similarity\n    class EuclideanDistanceStrategy < BaseStrategy\n      def similarity_between(one, other)\n        shared = shared_items_between(one, other)\n\n        return 0 if shared.empty?\n\n        sum_of_squares = shared.inject(0.0) { |sum, item|\n          sum + (preferences[one][item] - preferences[other][item]) ** 2\n        }\n\n        1 / (1 + sum_of_squares)\n      end\n    end\n  end\nend\n\n"
  },
  {
    "path": "lib/coletivo/similarity/pearson_correlation_strategy.rb",
    "content": "module Coletivo\n  module Similarity\n    class PearsonCorrelationStrategy < BaseStrategy\n      def similarity_between(one, other)\n        shared      = shared_items_between(one, other)\n        prefs_one   = preferences[one]\n        prefs_other = preferences[other]\n\n        return 0 if shared.empty?\n\n        sum_prefs_one = sum_prefs_other = sum_squares_one = \\\n          sum_squares_other = p_sum = 0.0\n\n        shared.each { |item|\n          sum_prefs_one     += prefs_one[item]\n          sum_prefs_other   += prefs_other[item]\n          sum_squares_one   += prefs_one[item] ** 2\n          sum_squares_other += prefs_other[item] ** 2\n          p_sum             += prefs_one[item] * prefs_other[item]\n        }\n\n        total_shared = shared.size\n\n        numerator = p_sum - (sum_prefs_one * sum_prefs_other / total_shared)\n\n        den_one = sum_squares_one - (sum_prefs_one ** 2) / total_shared\n        den_other = sum_squares_other - (sum_prefs_other ** 2) / total_shared\n\n        denominator = Math.sqrt(den_one * den_other)\n\n        denominator == 0 ? 0 : numerator / denominator\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/coletivo.rb",
    "content": "require 'rails'\nrequire 'active_model'\nrequire 'active_record'\nrequire 'active_support'\n\nmodule Coletivo\n  module Models\n    autoload :Recommendable, 'coletivo/models/recommendable'\n    autoload :Person, 'coletivo/models/person'\n    autoload :PersonRating, 'coletivo/models/person_rating'\n  end\n\n  module Similarity\n    NO_SIMILARITY = -1.0..0.49\n    SIMILAR = 0.5..0.99\n    IDENTICAL = 1.0\n\n    autoload :BaseStrategy, 'coletivo/similarity/base_strategy'\n    autoload :EuclideanDistanceStrategy, 'coletivo/similarity/euclidean_distance_strategy'\n    autoload :PearsonCorrelationStrategy, 'coletivo/similarity/pearson_correlation_strategy'\n    autoload :Engine, 'coletivo/similarity/engine'\n  end\n\n  module Config\n    mattr_accessor :ratings_container\n\n    # Defaults\n    self.ratings_container = Coletivo::Models::PersonRating\n  end\n\n  if defined?(Rails)\n    require 'coletivo/rails/engine'\n    require 'coletivo/rails/active_record'\n  end\nend\n"
  },
  {
    "path": "lib/generators/coletivo/coletivo_generator.rb",
    "content": "require 'rails/generators'\nrequire 'rails/generators/migration'\n\nclass ColetivoGenerator < Rails::Generators::Base\n  include Rails::Generators::Migration\n\n  def self.source_root\n    File.join(File.dirname(__FILE__), 'templates')\n  end\n\n  # Implement the required interface for Rails::Generators::Migration.\n  def self.next_migration_number(dirname) #:nodoc:\n    if ActiveRecord::Base.timestamped_migrations\n      Time.now.utc.strftime(\"%Y%m%d%H%M%S\")\n    else\n      \"%.3d\" % (current_migration_number(dirname) + 1)\n    end\n  end\n\n  def create_migration_file\n    migration_template 'person_ratings_migration.rb',\n      'db/migrate/create_person_ratings.rb'\n  end\nend\n"
  },
  {
    "path": "lib/generators/coletivo/templates/person_ratings_migration.rb",
    "content": "class CreatePersonRatings < ActiveRecord::Migration\n  def self.up\n    create_table :person_ratings do |t|\n      t.integer :person_id\n      t.string  :person_type\n\n      t.integer :rateable_id\n      t.string  :rateable_type\n\n      t.decimal :weight, :precision => 5, :scale => 2\n\n      t.timestamps\n    end\n\n    add_index :person_ratings, :rateable_type, :unique => false\n  end\n\n  def self.down\n    drop_table :person_ratings\n  end\nend\n"
  },
  {
    "path": "test/coletivo_test.rb",
    "content": "require 'helper'\n\nclass ColetivoTest < Test::Unit::TestCase\n  should \"be able to change the ratings container class\" do\n    config = Coletivo::Config.dup\n    config.ratings_container = Object\n\n    assert_equal Object, config.ratings_container\n  end\nend\n"
  },
  {
    "path": "test/db/schema.rb",
    "content": "require 'generators/coletivo/templates/person_ratings_migration'\n\nActiveRecord::Schema.define(:version => 1) do\n  CreatePersonRatings.up\n\n  create_table :users do |t|\n    t.string :name\n    t.string :email\n\n    t.timestamps\n  end\n\n  create_table :movies do |t|\n    t.string :name\n\n    t.timestamps\n  end\n\n  create_table :actors do |t|\n    t.string :name\n\n    t.timestamps\n  end\nend\n"
  },
  {
    "path": "test/helper.rb",
    "content": "require 'rubygems'\nrequire 'bundler'\nbegin\n  Bundler.setup(:default, :development)\nrescue Bundler::BundlerError => e\n  $stderr.puts e.message\n  $stderr.puts \"Run `bundle install` to install missing gems\"\n  exit e.status_code\nend\n\n$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))\n$LOAD_PATH.unshift(File.dirname(__FILE__))\nrequire 'coletivo'\n\nrequire 'test/unit'\nrequire 'turn'\nrequire 'shoulda'\n\nActiveRecord::Base.establish_connection({\n  :adapter => 'sqlite3',\n  :database => ':memory:'\n})\nActiveRecord::Migration.verbose = false\n\nrequire 'db/schema'\n\nclass Test::Unit::TestCase\nend\n"
  },
  {
    "path": "test/models/person_rating_test.rb",
    "content": "require 'helper'\nrequire 'models_helper'\n\nclass PersonRatingTest < Test::Unit::TestCase\n  subject { Coletivo::Models::PersonRating.new }\n\n  should validate_presence_of(:person)\n  should validate_presence_of(:rateable)\n  should validate_presence_of(:weight)\n\n  context \"#find_for_recommendation\" do\n    subject { Coletivo::Models::PersonRating }\n\n    should \"list only ratings of the type to recommend\" do\n      user = User.create(:name => 'A Good User')\n      movie = Movie.create(:name => 'The Tourist')\n      actress = Actor.create(:name => 'Angelina Jolie')\n\n      user.rate!(movie, 5.0)\n      user.rate!(actress, 10.0) # :-)\n\n      recommendations = subject.find_for_recommendation(user, Movie)\n\n      assert_equal 1, recommendations.size\n    end\n  end\nend\n"
  },
  {
    "path": "test/models/person_test.rb",
    "content": "require 'helper'\nrequire 'models_helper'\n\nclass PersonTest < Test::Unit::TestCase\n  def setup\n    super\n    @person = User.create(:name => 'Uber Geek')\n  end\n\n  should \"be able to rate an object\" do\n    movie  = Movie.create(:name => 'Lovely Movie')\n    @person.rate!(movie, 1)\n\n    assert_equal 1, ratings_container.all.size\n  end\nend\n"
  },
  {
    "path": "test/models/recommendable_test.rb",
    "content": "require 'helper'\nrequire 'models_helper'\n\nclass RecommendableTest < Test::Unit::TestCase\n  def setup\n    super\n\n    @person1 = User.create(:name => 'Person 1')\n    @person2 = User.create(:name => 'Person 2')\n  end\n\n  [:euclidean, :pearson].each do |strategy|\n    context \"using #{strategy.to_s.upcase}\" do\n      should \"matches perfect similarity when preferences are identical\" do\n        m1 = Movie.create(:name => 'Movie 1')\n        m2 = Movie.create(:name => 'Movie 2')\n\n        p = {\n          @person1.id => {m1.id => 2.5, m2.id => 1.0},\n          @person2.id => {m1.id => 2.5, m2.id => 1.0}\n        }\n\n        sim = similarity_between(@person1, @person2, p, strategy)\n\n        assert_equal Coletivo::Similarity::IDENTICAL, sim\n      end\n\n      should \"matches no similarity when preferences are very different\" do\n        m1 = Movie.create(:name => 'Movie 1')\n        m2 = Movie.create(:name => 'Movie 2')\n\n        p = {\n          @person1.id => {m1.id => 1.0, m2.id => 10.0},\n          @person2.id => {m1.id => 10.0, m2.id => 1.0}\n        }\n\n        sim = similarity_between(@person1, @person2, p, strategy)\n\n        assert Coletivo::Similarity::NO_SIMILARITY.include?(sim)\n      end\n\n      should \"matches similarity when preferences are similar\" do\n        m1 = Movie.create(:name => 'Movie 1')\n        m2 = Movie.create(:name => 'Movie 2')\n\n        p = {\n          @person1.id => {m1.id => 3.0, m2.id => 5.0},\n          @person2.id => {m1.id => 4.0, m2.id => 5.0}\n        }\n\n        sim = similarity_between(@person1, @person2, p, strategy)\n\n        assert Coletivo::Similarity::SIMILAR.include?(sim) ||\n               Coletivo::Similarity::IDENTICAL == sim\n      end\n\n      should \"recommend items for a person - sorted by better ratings\" do\n        person3 = User.create(:name => 'Person 3')\n\n        m1 = Movie.create(:name => 'Movie 1')\n        m2 = Movie.create(:name => 'Movie 2')\n        m3 = Movie.create(:name => 'Movie 3')\n        m4 = Movie.create(:name => 'Movie 4')\n\n        p = {\n          @person1.id => {m1.id => 2.0, m3.id => 1.0},\n          @person2.id => {m1.id => 1.5, m2.id => 4.7, m3.id => 1.5, m4.id => 2.5},\n          person3.id => {m1.id => 2.5, m2.id => 6.0, m3.id => 0.5, m4.id => 1.5}\n        }\n\n        recommendations = Movie.find_recommendations_for(@person1,\n          :preferences => p, :strategy => strategy)\n\n        assert recommendations.index(m2) < recommendations.index(m4)\n      end\n\n      should \"be able to recommend a limited number of items\" do\n        person3 = User.create(:name => 'Person 3')\n\n        m1 = Movie.create(:name => 'Movie 1')\n        m2 = Movie.create(:name => 'Movie 2')\n        m3 = Movie.create(:name => 'Movie 3')\n        m4 = Movie.create(:name => 'Movie 4')\n\n        p = {\n          @person1.id => {m1.id => 2.0, m3.id => 1.0},\n          @person2.id => {m1.id => 1.5, m2.id => 4.7, m3.id => 1.5, m4.id => 2.5},\n          person3.id => {m1.id => 2.5, m2.id => 6.0, m3.id => 0.5, m4.id => 1.5}\n        }\n\n        assert_equal 1, Movie.find_recommendations_for(@person1,\n          :preferences => p, :strategy => strategy, :limit => 1).size\n      end\n    end\n  end\n\n  def similarity_between(one, other, preferences, strategy)\n    one.similarity_with(other.id, :preferences => preferences,\n      :strategy => strategy)\n  end\nend\n"
  },
  {
    "path": "test/models_helper.rb",
    "content": "require 'coletivo'\n\nclass User < ActiveRecord::Base\n  has_own_preferences\nend\n\nclass Movie < ActiveRecord::Base\nend\n\nclass Actor < ActiveRecord::Base\nend\n\ndef ratings_container\n  Coletivo::Config.ratings_container\nend\n\nclass Test::Unit::TestCase\n  def setup\n    truncate! :person_ratings, :users, :movies, :actors\n  end\n\n  private\n\n  def truncate!(*tables)\n    [*tables].each do |t|\n      ActiveRecord::Base.connection.execute(\"DELETE FROM #{t}\")\n    end\n  end\nend\n"
  }
]