Full Code of diogenes/coletivo for AI

master cec01c4a9939 cached
28 files
23.6 KB
7.2k tokens
70 symbols
1 requests
Download .txt
Repository: diogenes/coletivo
Branch: master
Commit: cec01c4a9939
Files: 28
Total size: 23.6 KB

Directory structure:
gitextract_tjaaxpp2/

├── .circleci/
│   └── config.yml
├── .document
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.rdoc
├── Rakefile
├── VERSION
├── coletivo.gemspec
├── lib/
│   ├── coletivo/
│   │   ├── models/
│   │   │   ├── person.rb
│   │   │   ├── person_rating.rb
│   │   │   └── recommendable.rb
│   │   ├── rails/
│   │   │   ├── active_record.rb
│   │   │   └── engine.rb
│   │   └── similarity/
│   │       ├── base_strategy.rb
│   │       ├── engine.rb
│   │       ├── euclidean_distance_strategy.rb
│   │       └── pearson_correlation_strategy.rb
│   ├── coletivo.rb
│   └── generators/
│       └── coletivo/
│           ├── coletivo_generator.rb
│           └── templates/
│               └── person_ratings_migration.rb
└── test/
    ├── coletivo_test.rb
    ├── db/
    │   └── schema.rb
    ├── helper.rb
    ├── models/
    │   ├── person_rating_test.rb
    │   ├── person_test.rb
    │   └── recommendable_test.rb
    └── models_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
# Ruby CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-ruby/ for more details
#
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
       - image: circleci/ruby:2.4.1-node-browsers
      
      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/postgres:9.4

    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum "Gemfile.lock" }}
          # fallback to using the latest cache if no exact match is found
          - v1-dependencies-

      - run:
          name: install dependencies
          command: |
            bundle install --jobs=4 --retry=3 --path vendor/bundle

      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
        
      # Database setup
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      # run tests!
      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
            
            bundle exec rspec --format progress \
                            --format RspecJunitFormatter \
                            --out /tmp/test-results/rspec.xml \
                            --format progress \
                            $TEST_FILES

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results


================================================
FILE: .document
================================================
lib/**/*.rb
bin/*
- 
features/**/*.feature
LICENSE.txt


================================================
FILE: .gitignore
================================================
Gemfile.lock

# rdoc generated
rdoc

# yard generated
doc
.yardoc

# bundler
.bundle

# jeweler generated
pkg

# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 
#
# * Create a file at ~/.gitignore
# * Include files you want ignored
# * Run: git config --global core.excludesfile ~/.gitignore
#
# After doing this, these files will be ignored in all your git projects,
# saving you from having to 'pollute' every project you touch with them
#
# 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)
#
# For MacOS:
#
#.DS_Store

# For TextMate
#*.tmproj
#tmtags

# For emacs:
#*~
#\#*
#.\#*

# For vim:
#*.swp

# For redcar:
#.redcar

# For rubinius:
#*.rbc


================================================
FILE: Gemfile
================================================
source "http://rubygems.org"

gem "rails", ">= 3.0.7"

group :development do
  gem "shoulda", "~> 3.0.0.beta2"
  gem "bundler", "~> 1.15.4"
  gem "jeweler", "~> 1.6.2"
  gem "turn"
  gem "sqlite3"
  gem "rdoc"
end


================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2011 Diógenes Falcão

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.rdoc
================================================
= coletivo

A simple Rails 3 recommendations engine.
Coletivo 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.

== Installation:

  sudo gem install coletivo
  rails g coletivo
  rake db:migrate

== Usage:

At your Rails model that represents a person (can be an _User_, _Member_, or something like that):

  class User < ActiveRecord::Base
    has_own_preferences

    # ...
  end

So, a person can rate things:

  current_user = User.create(:name => 'Diogenes')
  movie = Movie.create(:name => 'The Tourist', :year => 2010)

  current_user.rate!(movie, 4.5)

And after a lot of ratings... *recommendations*:

  Movie.find_recommendations_for(current_user) # => movies and more movies...

By default, the similarity strategy used is Euclidean Distance, but you can change that passing the _strategy_ option:
  Movie.find_recommendations_for(current_user, :strategy => :pearson)


== Contributing to coletivo
 
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
* Fork the project
* Start a feature/bugfix branch
* Commit and push until you are happy with your contribution
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
* 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.

== Copyright

Copyright (c) 2011 Diógenes Falcão. See LICENSE.txt for
further details.


================================================
FILE: Rakefile
================================================
# encoding: utf-8

require 'rubygems'
require 'bundler'
begin
  Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
  $stderr.puts e.message
  $stderr.puts "Run `bundle install` to install missing gems"
  exit e.status_code
end
require 'rake'

require 'jeweler'
Jeweler::Tasks.new do |gem|
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
  gem.name = "coletivo"
  gem.homepage = "http://github.com/diogenes/coletivo"
  gem.license = "MIT"
  gem.summary = %Q{A simple Rails 3 recommendations engine}
  gem.description = %Q{A simple Rails 3 recommendations engine}
  gem.email = "diogenes.araujo@gmail.com"
  gem.authors = ["Diógenes Falcão"]
  gem.files = Dir["{lib}/**/*"]
  # dependencies defined in Gemfile
end
Jeweler::RubygemsDotOrgTasks.new

require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
  test.libs << 'lib' << 'test'
  test.pattern = 'test/**/*_test.rb'
  test.verbose = true
end

task :default => :test

require 'rake/rdoctask'
Rake::RDocTask.new do |rdoc|
  version = File.exist?('VERSION') ? File.read('VERSION') : ""

  rdoc.rdoc_dir = 'rdoc'
  rdoc.title = "coletivo #{version}"
  rdoc.rdoc_files.include('README*')
  rdoc.rdoc_files.include('lib/**/*.rb')
end


================================================
FILE: VERSION
================================================
0.0.3

================================================
FILE: coletivo.gemspec
================================================
# Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
# -*- encoding: utf-8 -*-

Gem::Specification.new do |s|
  s.name = %q{coletivo}
  s.version = "0.0.3"

  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
  s.authors = ["Di\303\263genes Falc\303\243o"]
  s.date = %q{2011-10-31}
  s.description = %q{A simple Rails 3 recommendations engine}
  s.email = %q{diogenes.araujo@gmail.com}
  s.extra_rdoc_files = [
    "LICENSE.txt",
    "README.rdoc"
  ]
  s.files = [
    "lib/coletivo.rb",
    "lib/coletivo/models/person.rb",
    "lib/coletivo/models/person_rating.rb",
    "lib/coletivo/models/recommendable.rb",
    "lib/coletivo/rails/active_record.rb",
    "lib/coletivo/rails/engine.rb",
    "lib/coletivo/similarity/base_strategy.rb",
    "lib/coletivo/similarity/engine.rb",
    "lib/coletivo/similarity/euclidean_distance_strategy.rb",
    "lib/coletivo/similarity/pearson_correlation_strategy.rb",
    "lib/generators/coletivo/coletivo_generator.rb",
    "lib/generators/coletivo/templates/person_ratings_migration.rb"
  ]
  s.homepage = %q{http://github.com/diogenes/coletivo}
  s.licenses = ["MIT"]
  s.require_paths = ["lib"]
  s.rubygems_version = %q{1.3.7}
  s.summary = %q{A simple Rails 3 recommendations engine}

  if s.respond_to? :specification_version then
    current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
    s.specification_version = 3

    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
      s.add_runtime_dependency(%q<rails>, [">= 3.0.7"])
      s.add_development_dependency(%q<shoulda>, ["~> 3.0.0.beta2"])
      s.add_development_dependency(%q<bundler>, ["~> 1.0.14"])
      s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
      s.add_development_dependency(%q<turn>, [">= 0"])
      s.add_development_dependency(%q<sqlite3>, [">= 0"])
    else
      s.add_dependency(%q<rails>, [">= 3.0.7"])
      s.add_dependency(%q<shoulda>, ["~> 3.0.0.beta2"])
      s.add_dependency(%q<bundler>, ["~> 1.0.14"])
      s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
      s.add_dependency(%q<turn>, [">= 0"])
      s.add_dependency(%q<sqlite3>, [">= 0"])
    end
  else
    s.add_dependency(%q<rails>, [">= 3.0.7"])
    s.add_dependency(%q<shoulda>, ["~> 3.0.0.beta2"])
    s.add_dependency(%q<bundler>, ["~> 1.0.14"])
    s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
    s.add_dependency(%q<turn>, [">= 0"])
    s.add_dependency(%q<sqlite3>, [">= 0"])
  end
end



================================================
FILE: lib/coletivo/models/person.rb
================================================
module Coletivo
  module Models
    module Person
      def self.included(base)
        base.extend ClassMethods
      end

      module ClassMethods
        # TODO: has_own_preferences doc.
        def has_own_preferences(options = {})
          self.send :include, InstanceMethods
        end
      end # ClassMethods

      module InstanceMethods
        def rate!(rateable, weight)
          Coletivo::Config.ratings_container.create!({
            :person => self,
            :rateable => rateable,
            :weight => weight
          })
        end
      end # InstanceMethods

    end # Person
  end # Models
end


================================================
FILE: lib/coletivo/models/person_rating.rb
================================================
module Coletivo
  module Models
    class PersonRating < ActiveRecord::Base
      belongs_to :person, :polymorphic => true
      belongs_to :rateable, :polymorphic => true

      validates :person, :rateable, :weight, :presence => true

      def self.find_for_recommendation(person, rateable_type)
        where(:rateable_type => rateable_type.to_s)
      end
    end
  end
end


================================================
FILE: lib/coletivo/models/recommendable.rb
================================================
module Coletivo
  module Models
    module Recommendable
      def self.included(base)
        base.extend ClassMethods
        base.send :include, InstanceMethods
      end

      module ClassMethods
        def find_recommendations_for(person, options = {})
          preferences = options[:preferences] ||=
            load_preferences_for_recommendation(person)
          top = predict_highest_ratings(person, preferences, options)
          ids = top.collect(&:last)

          where(:id => ids).limit(options[:limit]).all
        end

        def map_ratings_to_preferences(ratings)
          #TODO: (???) Item based mapping.
          key, subkey = :person_id, :rateable_id
          preferences = {}

          ratings.each do |rating|
            p = preferences[rating.send(key)] ||= {}
            p[rating.send(subkey)] = rating.weight
          end

          preferences
        end

        def load_preferences_for_recommendation(person)
          r = Coletivo::Config.ratings_container\
                .find_for_recommendation(person, self)

          map_ratings_to_preferences(r)
        end

        private

        def predict_highest_ratings(person, people_preferences, options)
          data = {}
          people_preferences.each do |other, other_prefs|
            next if other == person

            sim = person.similarity_with(other, options)
            next if sim <= 0

            other_prefs.each do |item, weight|
              unless people_preferences[person.id].keys.include?(item)
                data[item] ||= {:total_similarity => 0.0, :weighted_mean => 0.0}
                data[item][:total_similarity] += sim
                data[item][:weighted_mean] += weight * sim
              end
            end
          end

          # e.g: [[5.35, "movie_2"], [2.0, "movie_4"]]
          guessed_rating_and_id = Proc.new do |item, item_data|
            [item_data[:weighted_mean] / item_data[:total_similarity], item]
          end

          # DESC sorting by weighted mean of ratings
          data.collect(&guessed_rating_and_id).sort_by(&:first).reverse
        end
      end

      module InstanceMethods
        def similarity_with(other_id, options = {})
          p = options[:preferences] ||
            self.class.load_preferences_for_recommendation(self)

          Coletivo::Similarity::Engine\
            .similarity_between(self.id, other_id, p, options)
        end
      end
    end # Recommendable
  end # Models
end


================================================
FILE: lib/coletivo/rails/active_record.rb
================================================
ActiveRecord::Base.send :include, Coletivo::Models::Person
ActiveRecord::Base.send :include, Coletivo::Models::Recommendable


================================================
FILE: lib/coletivo/rails/engine.rb
================================================
require 'coletivo'

module Coletivo
  class Engine < Rails::Engine
  end
end


================================================
FILE: lib/coletivo/similarity/base_strategy.rb
================================================
module Coletivo
  module Similarity
    class BaseStrategy
      attr_accessor :preferences

      def similarity_between(one, other)
        raise "The #similarity_between was not implemented in #{self.class}"
      end

      def train_with(people_preferences)
        @preferences = people_preferences
      end

      protected

      def shared_items_between(one, other)
        return [] unless preferences[one] && preferences[other]

        preferences[one].keys.select { |item|
          preferences[other].keys.include? item
        }
      end
    end
  end # Similarity
end # Coletivo


================================================
FILE: lib/coletivo/similarity/engine.rb
================================================
module Coletivo
  module Similarity
    class Engine
      def self.similarity_between(one, other, preferences, options = {})
        strategy = load_strategy options[:strategy]
        strategy.train_with(preferences)

        strategy.similarity_between(one, other)
      end

      protected

      def self.load_strategy(key)
        if :pearson == key
          Coletivo::Similarity::PearsonCorrelationStrategy.new
        else
          Coletivo::Similarity::EuclideanDistanceStrategy.new
        end
      end
    end # Engine
  end # Similarity
end


================================================
FILE: lib/coletivo/similarity/euclidean_distance_strategy.rb
================================================
module Coletivo
  module Similarity
    class EuclideanDistanceStrategy < BaseStrategy
      def similarity_between(one, other)
        shared = shared_items_between(one, other)

        return 0 if shared.empty?

        sum_of_squares = shared.inject(0.0) { |sum, item|
          sum + (preferences[one][item] - preferences[other][item]) ** 2
        }

        1 / (1 + sum_of_squares)
      end
    end
  end
end



================================================
FILE: lib/coletivo/similarity/pearson_correlation_strategy.rb
================================================
module Coletivo
  module Similarity
    class PearsonCorrelationStrategy < BaseStrategy
      def similarity_between(one, other)
        shared      = shared_items_between(one, other)
        prefs_one   = preferences[one]
        prefs_other = preferences[other]

        return 0 if shared.empty?

        sum_prefs_one = sum_prefs_other = sum_squares_one = \
          sum_squares_other = p_sum = 0.0

        shared.each { |item|
          sum_prefs_one     += prefs_one[item]
          sum_prefs_other   += prefs_other[item]
          sum_squares_one   += prefs_one[item] ** 2
          sum_squares_other += prefs_other[item] ** 2
          p_sum             += prefs_one[item] * prefs_other[item]
        }

        total_shared = shared.size

        numerator = p_sum - (sum_prefs_one * sum_prefs_other / total_shared)

        den_one = sum_squares_one - (sum_prefs_one ** 2) / total_shared
        den_other = sum_squares_other - (sum_prefs_other ** 2) / total_shared

        denominator = Math.sqrt(den_one * den_other)

        denominator == 0 ? 0 : numerator / denominator
      end
    end
  end
end


================================================
FILE: lib/coletivo.rb
================================================
require 'rails'
require 'active_model'
require 'active_record'
require 'active_support'

module Coletivo
  module Models
    autoload :Recommendable, 'coletivo/models/recommendable'
    autoload :Person, 'coletivo/models/person'
    autoload :PersonRating, 'coletivo/models/person_rating'
  end

  module Similarity
    NO_SIMILARITY = -1.0..0.49
    SIMILAR = 0.5..0.99
    IDENTICAL = 1.0

    autoload :BaseStrategy, 'coletivo/similarity/base_strategy'
    autoload :EuclideanDistanceStrategy, 'coletivo/similarity/euclidean_distance_strategy'
    autoload :PearsonCorrelationStrategy, 'coletivo/similarity/pearson_correlation_strategy'
    autoload :Engine, 'coletivo/similarity/engine'
  end

  module Config
    mattr_accessor :ratings_container

    # Defaults
    self.ratings_container = Coletivo::Models::PersonRating
  end

  if defined?(Rails)
    require 'coletivo/rails/engine'
    require 'coletivo/rails/active_record'
  end
end


================================================
FILE: lib/generators/coletivo/coletivo_generator.rb
================================================
require 'rails/generators'
require 'rails/generators/migration'

class ColetivoGenerator < Rails::Generators::Base
  include Rails::Generators::Migration

  def self.source_root
    File.join(File.dirname(__FILE__), 'templates')
  end

  # Implement the required interface for Rails::Generators::Migration.
  def self.next_migration_number(dirname) #:nodoc:
    if ActiveRecord::Base.timestamped_migrations
      Time.now.utc.strftime("%Y%m%d%H%M%S")
    else
      "%.3d" % (current_migration_number(dirname) + 1)
    end
  end

  def create_migration_file
    migration_template 'person_ratings_migration.rb',
      'db/migrate/create_person_ratings.rb'
  end
end


================================================
FILE: lib/generators/coletivo/templates/person_ratings_migration.rb
================================================
class CreatePersonRatings < ActiveRecord::Migration
  def self.up
    create_table :person_ratings do |t|
      t.integer :person_id
      t.string  :person_type

      t.integer :rateable_id
      t.string  :rateable_type

      t.decimal :weight, :precision => 5, :scale => 2

      t.timestamps
    end

    add_index :person_ratings, :rateable_type, :unique => false
  end

  def self.down
    drop_table :person_ratings
  end
end


================================================
FILE: test/coletivo_test.rb
================================================
require 'helper'

class ColetivoTest < Test::Unit::TestCase
  should "be able to change the ratings container class" do
    config = Coletivo::Config.dup
    config.ratings_container = Object

    assert_equal Object, config.ratings_container
  end
end


================================================
FILE: test/db/schema.rb
================================================
require 'generators/coletivo/templates/person_ratings_migration'

ActiveRecord::Schema.define(:version => 1) do
  CreatePersonRatings.up

  create_table :users do |t|
    t.string :name
    t.string :email

    t.timestamps
  end

  create_table :movies do |t|
    t.string :name

    t.timestamps
  end

  create_table :actors do |t|
    t.string :name

    t.timestamps
  end
end


================================================
FILE: test/helper.rb
================================================
require 'rubygems'
require 'bundler'
begin
  Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
  $stderr.puts e.message
  $stderr.puts "Run `bundle install` to install missing gems"
  exit e.status_code
end

$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'coletivo'

require 'test/unit'
require 'turn'
require 'shoulda'

ActiveRecord::Base.establish_connection({
  :adapter => 'sqlite3',
  :database => ':memory:'
})
ActiveRecord::Migration.verbose = false

require 'db/schema'

class Test::Unit::TestCase
end


================================================
FILE: test/models/person_rating_test.rb
================================================
require 'helper'
require 'models_helper'

class PersonRatingTest < Test::Unit::TestCase
  subject { Coletivo::Models::PersonRating.new }

  should validate_presence_of(:person)
  should validate_presence_of(:rateable)
  should validate_presence_of(:weight)

  context "#find_for_recommendation" do
    subject { Coletivo::Models::PersonRating }

    should "list only ratings of the type to recommend" do
      user = User.create(:name => 'A Good User')
      movie = Movie.create(:name => 'The Tourist')
      actress = Actor.create(:name => 'Angelina Jolie')

      user.rate!(movie, 5.0)
      user.rate!(actress, 10.0) # :-)

      recommendations = subject.find_for_recommendation(user, Movie)

      assert_equal 1, recommendations.size
    end
  end
end


================================================
FILE: test/models/person_test.rb
================================================
require 'helper'
require 'models_helper'

class PersonTest < Test::Unit::TestCase
  def setup
    super
    @person = User.create(:name => 'Uber Geek')
  end

  should "be able to rate an object" do
    movie  = Movie.create(:name => 'Lovely Movie')
    @person.rate!(movie, 1)

    assert_equal 1, ratings_container.all.size
  end
end


================================================
FILE: test/models/recommendable_test.rb
================================================
require 'helper'
require 'models_helper'

class RecommendableTest < Test::Unit::TestCase
  def setup
    super

    @person1 = User.create(:name => 'Person 1')
    @person2 = User.create(:name => 'Person 2')
  end

  [:euclidean, :pearson].each do |strategy|
    context "using #{strategy.to_s.upcase}" do
      should "matches perfect similarity when preferences are identical" do
        m1 = Movie.create(:name => 'Movie 1')
        m2 = Movie.create(:name => 'Movie 2')

        p = {
          @person1.id => {m1.id => 2.5, m2.id => 1.0},
          @person2.id => {m1.id => 2.5, m2.id => 1.0}
        }

        sim = similarity_between(@person1, @person2, p, strategy)

        assert_equal Coletivo::Similarity::IDENTICAL, sim
      end

      should "matches no similarity when preferences are very different" do
        m1 = Movie.create(:name => 'Movie 1')
        m2 = Movie.create(:name => 'Movie 2')

        p = {
          @person1.id => {m1.id => 1.0, m2.id => 10.0},
          @person2.id => {m1.id => 10.0, m2.id => 1.0}
        }

        sim = similarity_between(@person1, @person2, p, strategy)

        assert Coletivo::Similarity::NO_SIMILARITY.include?(sim)
      end

      should "matches similarity when preferences are similar" do
        m1 = Movie.create(:name => 'Movie 1')
        m2 = Movie.create(:name => 'Movie 2')

        p = {
          @person1.id => {m1.id => 3.0, m2.id => 5.0},
          @person2.id => {m1.id => 4.0, m2.id => 5.0}
        }

        sim = similarity_between(@person1, @person2, p, strategy)

        assert Coletivo::Similarity::SIMILAR.include?(sim) ||
               Coletivo::Similarity::IDENTICAL == sim
      end

      should "recommend items for a person - sorted by better ratings" do
        person3 = User.create(:name => 'Person 3')

        m1 = Movie.create(:name => 'Movie 1')
        m2 = Movie.create(:name => 'Movie 2')
        m3 = Movie.create(:name => 'Movie 3')
        m4 = Movie.create(:name => 'Movie 4')

        p = {
          @person1.id => {m1.id => 2.0, m3.id => 1.0},
          @person2.id => {m1.id => 1.5, m2.id => 4.7, m3.id => 1.5, m4.id => 2.5},
          person3.id => {m1.id => 2.5, m2.id => 6.0, m3.id => 0.5, m4.id => 1.5}
        }

        recommendations = Movie.find_recommendations_for(@person1,
          :preferences => p, :strategy => strategy)

        assert recommendations.index(m2) < recommendations.index(m4)
      end

      should "be able to recommend a limited number of items" do
        person3 = User.create(:name => 'Person 3')

        m1 = Movie.create(:name => 'Movie 1')
        m2 = Movie.create(:name => 'Movie 2')
        m3 = Movie.create(:name => 'Movie 3')
        m4 = Movie.create(:name => 'Movie 4')

        p = {
          @person1.id => {m1.id => 2.0, m3.id => 1.0},
          @person2.id => {m1.id => 1.5, m2.id => 4.7, m3.id => 1.5, m4.id => 2.5},
          person3.id => {m1.id => 2.5, m2.id => 6.0, m3.id => 0.5, m4.id => 1.5}
        }

        assert_equal 1, Movie.find_recommendations_for(@person1,
          :preferences => p, :strategy => strategy, :limit => 1).size
      end
    end
  end

  def similarity_between(one, other, preferences, strategy)
    one.similarity_with(other.id, :preferences => preferences,
      :strategy => strategy)
  end
end


================================================
FILE: test/models_helper.rb
================================================
require 'coletivo'

class User < ActiveRecord::Base
  has_own_preferences
end

class Movie < ActiveRecord::Base
end

class Actor < ActiveRecord::Base
end

def ratings_container
  Coletivo::Config.ratings_container
end

class Test::Unit::TestCase
  def setup
    truncate! :person_ratings, :users, :movies, :actors
  end

  private

  def truncate!(*tables)
    [*tables].each do |t|
      ActiveRecord::Base.connection.execute("DELETE FROM #{t}")
    end
  end
end
Download .txt
gitextract_tjaaxpp2/

├── .circleci/
│   └── config.yml
├── .document
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.rdoc
├── Rakefile
├── VERSION
├── coletivo.gemspec
├── lib/
│   ├── coletivo/
│   │   ├── models/
│   │   │   ├── person.rb
│   │   │   ├── person_rating.rb
│   │   │   └── recommendable.rb
│   │   ├── rails/
│   │   │   ├── active_record.rb
│   │   │   └── engine.rb
│   │   └── similarity/
│   │       ├── base_strategy.rb
│   │       ├── engine.rb
│   │       ├── euclidean_distance_strategy.rb
│   │       └── pearson_correlation_strategy.rb
│   ├── coletivo.rb
│   └── generators/
│       └── coletivo/
│           ├── coletivo_generator.rb
│           └── templates/
│               └── person_ratings_migration.rb
└── test/
    ├── coletivo_test.rb
    ├── db/
    │   └── schema.rb
    ├── helper.rb
    ├── models/
    │   ├── person_rating_test.rb
    │   ├── person_test.rb
    │   └── recommendable_test.rb
    └── models_helper.rb
Download .txt
SYMBOL INDEX (70 symbols across 17 files)

FILE: lib/coletivo.rb
  type Coletivo (line 6) | module Coletivo
    type Models (line 7) | module Models
    type Similarity (line 13) | module Similarity
    type Config (line 24) | module Config

FILE: lib/coletivo/models/person.rb
  type Coletivo (line 1) | module Coletivo
    type Models (line 2) | module Models
      type Person (line 3) | module Person
        function included (line 4) | def self.included(base)
        type ClassMethods (line 8) | module ClassMethods
          function has_own_preferences (line 10) | def has_own_preferences(options = {})
        type InstanceMethods (line 15) | module InstanceMethods
          function rate! (line 16) | def rate!(rateable, weight)

FILE: lib/coletivo/models/person_rating.rb
  type Coletivo (line 1) | module Coletivo
    type Models (line 2) | module Models
      class PersonRating (line 3) | class PersonRating < ActiveRecord::Base
        method find_for_recommendation (line 9) | def self.find_for_recommendation(person, rateable_type)

FILE: lib/coletivo/models/recommendable.rb
  type Coletivo (line 1) | module Coletivo
    type Models (line 2) | module Models
      type Recommendable (line 3) | module Recommendable
        function included (line 4) | def self.included(base)
        type ClassMethods (line 9) | module ClassMethods
          function find_recommendations_for (line 10) | def find_recommendations_for(person, options = {})
          function map_ratings_to_preferences (line 19) | def map_ratings_to_preferences(ratings)
          function load_preferences_for_recommendation (line 32) | def load_preferences_for_recommendation(person)
          function predict_highest_ratings (line 41) | def predict_highest_ratings(person, people_preferences, options)
        type InstanceMethods (line 68) | module InstanceMethods
          function similarity_with (line 69) | def similarity_with(other_id, options = {})

FILE: lib/coletivo/rails/engine.rb
  type Coletivo (line 3) | module Coletivo
    class Engine (line 4) | class Engine < Rails::Engine

FILE: lib/coletivo/similarity/base_strategy.rb
  type Coletivo (line 1) | module Coletivo
    type Similarity (line 2) | module Similarity
      class BaseStrategy (line 3) | class BaseStrategy
        method similarity_between (line 6) | def similarity_between(one, other)
        method train_with (line 10) | def train_with(people_preferences)
        method shared_items_between (line 16) | def shared_items_between(one, other)

FILE: lib/coletivo/similarity/engine.rb
  type Coletivo (line 1) | module Coletivo
    type Similarity (line 2) | module Similarity
      class Engine (line 3) | class Engine
        method similarity_between (line 4) | def self.similarity_between(one, other, preferences, options = {})
        method load_strategy (line 13) | def self.load_strategy(key)

FILE: lib/coletivo/similarity/euclidean_distance_strategy.rb
  type Coletivo (line 1) | module Coletivo
    type Similarity (line 2) | module Similarity
      class EuclideanDistanceStrategy (line 3) | class EuclideanDistanceStrategy < BaseStrategy
        method similarity_between (line 4) | def similarity_between(one, other)

FILE: lib/coletivo/similarity/pearson_correlation_strategy.rb
  type Coletivo (line 1) | module Coletivo
    type Similarity (line 2) | module Similarity
      class PearsonCorrelationStrategy (line 3) | class PearsonCorrelationStrategy < BaseStrategy
        method similarity_between (line 4) | def similarity_between(one, other)

FILE: lib/generators/coletivo/coletivo_generator.rb
  class ColetivoGenerator (line 4) | class ColetivoGenerator < Rails::Generators::Base
    method source_root (line 7) | def self.source_root
    method next_migration_number (line 12) | def self.next_migration_number(dirname) #:nodoc:
    method create_migration_file (line 20) | def create_migration_file

FILE: lib/generators/coletivo/templates/person_ratings_migration.rb
  class CreatePersonRatings (line 1) | class CreatePersonRatings < ActiveRecord::Migration
    method up (line 2) | def self.up
    method down (line 18) | def self.down

FILE: test/coletivo_test.rb
  class ColetivoTest (line 3) | class ColetivoTest < Test::Unit::TestCase

FILE: test/helper.rb
  class Test::Unit::TestCase (line 27) | class Test::Unit::TestCase

FILE: test/models/person_rating_test.rb
  class PersonRatingTest (line 4) | class PersonRatingTest < Test::Unit::TestCase

FILE: test/models/person_test.rb
  class PersonTest (line 4) | class PersonTest < Test::Unit::TestCase
    method setup (line 5) | def setup

FILE: test/models/recommendable_test.rb
  class RecommendableTest (line 4) | class RecommendableTest < Test::Unit::TestCase
    method setup (line 5) | def setup
    method similarity_between (line 97) | def similarity_between(one, other, preferences, strategy)

FILE: test/models_helper.rb
  class User (line 3) | class User < ActiveRecord::Base
  class Movie (line 7) | class Movie < ActiveRecord::Base
  class Actor (line 10) | class Actor < ActiveRecord::Base
  function ratings_container (line 13) | def ratings_container
  class Test::Unit::TestCase (line 17) | class Test::Unit::TestCase
    method setup (line 18) | def setup
    method truncate! (line 24) | def truncate!(*tables)
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (27K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 1840,
    "preview": "# Ruby CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-ruby/ for more details\n#\nversion"
  },
  {
    "path": ".document",
    "chars": 55,
    "preview": "lib/**/*.rb\nbin/*\n- \nfeatures/**/*.feature\nLICENSE.txt\n"
  },
  {
    "path": ".gitignore",
    "chars": 793,
    "preview": "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 ed"
  },
  {
    "path": "Gemfile",
    "chars": 214,
    "preview": "source \"http://rubygems.org\"\n\ngem \"rails\", \">= 3.0.7\"\n\ngroup :development do\n  gem \"shoulda\", \"~> 3.0.0.beta2\"\n  gem \"bu"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1059,
    "preview": "Copyright (c) 2011 Diógenes Falcão\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this"
  },
  {
    "path": "README.rdoc",
    "chars": 1854,
    "preview": "= coletivo\n\nA simple Rails 3 recommendations engine.\nColetivo uses {Euclidean Distance}[http://en.wikipedia.org/wiki/Euc"
  },
  {
    "path": "Rakefile",
    "chars": 1256,
    "preview": "# encoding: utf-8\n\nrequire 'rubygems'\nrequire 'bundler'\nbegin\n  Bundler.setup(:default, :development)\nrescue Bundler::Bu"
  },
  {
    "path": "VERSION",
    "chars": 5,
    "preview": "0.0.3"
  },
  {
    "path": "coletivo.gemspec",
    "chars": 2552,
    "preview": "# Generated by jeweler\n# DO NOT EDIT THIS FILE DIRECTLY\n# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspe"
  },
  {
    "path": "lib/coletivo/models/person.rb",
    "chars": 625,
    "preview": "module Coletivo\n  module Models\n    module Person\n      def self.included(base)\n        base.extend ClassMethods\n      e"
  },
  {
    "path": "lib/coletivo/models/person_rating.rb",
    "chars": 379,
    "preview": "module Coletivo\n  module Models\n    class PersonRating < ActiveRecord::Base\n      belongs_to :person, :polymorphic => tr"
  },
  {
    "path": "lib/coletivo/models/recommendable.rb",
    "chars": 2478,
    "preview": "module Coletivo\n  module Models\n    module Recommendable\n      def self.included(base)\n        base.extend ClassMethods\n"
  },
  {
    "path": "lib/coletivo/rails/active_record.rb",
    "chars": 125,
    "preview": "ActiveRecord::Base.send :include, Coletivo::Models::Person\nActiveRecord::Base.send :include, Coletivo::Models::Recommend"
  },
  {
    "path": "lib/coletivo/rails/engine.rb",
    "chars": 77,
    "preview": "require 'coletivo'\n\nmodule Coletivo\n  class Engine < Rails::Engine\n  end\nend\n"
  },
  {
    "path": "lib/coletivo/similarity/base_strategy.rb",
    "chars": 597,
    "preview": "module Coletivo\n  module Similarity\n    class BaseStrategy\n      attr_accessor :preferences\n\n      def similarity_betwee"
  },
  {
    "path": "lib/coletivo/similarity/engine.rb",
    "chars": 557,
    "preview": "module Coletivo\n  module Similarity\n    class Engine\n      def self.similarity_between(one, other, preferences, options "
  },
  {
    "path": "lib/coletivo/similarity/euclidean_distance_strategy.rb",
    "chars": 418,
    "preview": "module Coletivo\n  module Similarity\n    class EuclideanDistanceStrategy < BaseStrategy\n      def similarity_between(one,"
  },
  {
    "path": "lib/coletivo/similarity/pearson_correlation_strategy.rb",
    "chars": 1116,
    "preview": "module Coletivo\n  module Similarity\n    class PearsonCorrelationStrategy < BaseStrategy\n      def similarity_between(one"
  },
  {
    "path": "lib/coletivo.rb",
    "chars": 945,
    "preview": "require 'rails'\nrequire 'active_model'\nrequire 'active_record'\nrequire 'active_support'\n\nmodule Coletivo\n  module Models"
  },
  {
    "path": "lib/generators/coletivo/coletivo_generator.rb",
    "chars": 666,
    "preview": "require 'rails/generators'\nrequire 'rails/generators/migration'\n\nclass ColetivoGenerator < Rails::Generators::Base\n  inc"
  },
  {
    "path": "lib/generators/coletivo/templates/person_ratings_migration.rb",
    "chars": 435,
    "preview": "class CreatePersonRatings < ActiveRecord::Migration\n  def self.up\n    create_table :person_ratings do |t|\n      t.intege"
  },
  {
    "path": "test/coletivo_test.rb",
    "chars": 253,
    "preview": "require 'helper'\n\nclass ColetivoTest < Test::Unit::TestCase\n  should \"be able to change the ratings container class\" do\n"
  },
  {
    "path": "test/db/schema.rb",
    "chars": 382,
    "preview": "require 'generators/coletivo/templates/person_ratings_migration'\n\nActiveRecord::Schema.define(:version => 1) do\n  Create"
  },
  {
    "path": "test/helper.rb",
    "chars": 603,
    "preview": "require 'rubygems'\nrequire 'bundler'\nbegin\n  Bundler.setup(:default, :development)\nrescue Bundler::BundlerError => e\n  $"
  },
  {
    "path": "test/models/person_rating_test.rb",
    "chars": 761,
    "preview": "require 'helper'\nrequire 'models_helper'\n\nclass PersonRatingTest < Test::Unit::TestCase\n  subject { Coletivo::Models::Pe"
  },
  {
    "path": "test/models/person_test.rb",
    "chars": 336,
    "preview": "require 'helper'\nrequire 'models_helper'\n\nclass PersonTest < Test::Unit::TestCase\n  def setup\n    super\n    @person = Us"
  },
  {
    "path": "test/models/recommendable_test.rb",
    "chars": 3303,
    "preview": "require 'helper'\nrequire 'models_helper'\n\nclass RecommendableTest < Test::Unit::TestCase\n  def setup\n    super\n\n    @per"
  },
  {
    "path": "test/models_helper.rb",
    "chars": 465,
    "preview": "require 'coletivo'\n\nclass User < ActiveRecord::Base\n  has_own_preferences\nend\n\nclass Movie < ActiveRecord::Base\nend\n\ncla"
  }
]

About this extraction

This page contains the full source code of the diogenes/coletivo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (23.6 KB), approximately 7.2k tokens, and a symbol index with 70 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!