diff --git a/app/models/photo.rb b/app/models/photo.rb index db5c2b6..7b44db7 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -14,8 +14,8 @@ class Photo < ActiveRecord::Base before_create :hashify_email before_save :set_oneness - after_create :create_directories - before_destroy :destroy_directories + after_create :create_directories, :if => lambda { |x| x.facebook_id.nil? } + before_destroy :destroy_directories, :if => lambda { |x| x.facebook_id.nil? } ## # Returns the path of the image relative to Merb's root. @@ -77,6 +77,7 @@ class Photo < ActiveRecord::Base # Checks to make sure that the file exists and is an image. # def validate_image_sanity + return true if self.facebook_id if self.file.empty? or self.file[:tempfile].nil? self.errors.add(:file, 'is not a file') elsif self.file[:content_type] !~ /image\/\w+/ diff --git a/app/models/user.rb b/app/models/user.rb index cb2942f..91355ee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,10 +4,11 @@ class User < ActiveRecord::Base attr_protected :auth_token attr_protected :authorized - validates_presence_of :user_name - validates_length_of :user_name, :within => (6..32) - validates_uniqueness_of :user_name - validates_format_of :user_name, :with => /[\w_-]+/ + validates_presence_of :user_name, :if => lambda { |x| x.facebook_id.nil? } + validates_length_of :user_name, :within => (6..32), :if => lambda { |x| x.facebook_id.nil? } + validates_uniqueness_of :user_name, :if => lambda { |x| x.facebook_id.nil? } + validates_format_of :user_name, :with => /[\w_-]+/, :if => lambda { |x| x.facebook_id.nil? } + validates_presence_of :facebook_id, :if => lambda { |x| x.user_name.to_s.empty? } has_many :photos, :dependent => :destroy has_many :votes, :dependent => :destroy, :order => 'votes.photo_id ASC' @@ -28,7 +29,7 @@ class User < ActiveRecord::Base end def self.salted_string(str) - Digest::SHA1.hexdigest("#{Merb::Config[:session_secret_key]}--#{str}--") + Digest::SHA1.hexdigest("#{@@salt}--#{str}--") end def voted_for?(photo) @@ -39,6 +40,7 @@ class User < ActiveRecord::Base protected def saltify_password + return true unless self.facebook_id.nil? if !self.password.to_s.empty? if self.password.to_s.size < 6 self.errors.add(:password, 'is too short') @@ -51,4 +53,8 @@ class User < ActiveRecord::Base self.errors.add(:password, 'is missing') end end + + private + + @@salt = '297d94827e16917483c130b7e7f4fd44d605dcdb' end diff --git a/app/models/vote.rb b/app/models/vote.rb index f244453..e983a9f 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -3,8 +3,9 @@ class Vote < ActiveRecord::Base belongs_to :user validates_presence_of :photo_id + validate :photo_id_cannot_be_zero validates_uniqueness_of :user_id, :scope => :photo_id - validates_uniqueness_of :session_id, :scope => :photo_id + validates_uniqueness_of :session_id, :scope => [ :photo_id, :user_id ] validates_presence_of :session_id, :if => lambda { |x| x.user_id.to_s.empty? } validates_presence_of :user_id, :if => lambda { |x| x.session_id.to_s.empty? } @@ -74,6 +75,14 @@ class Vote < ActiveRecord::Base protected + ## + # Checks to make sure the photo_id is not zero. There will never be a zero + # id of a Photo. + # + def photo_id_cannot_be_zero + self.errors.add :photo_id, 'cannot be zero' if self.photo_id.to_i == 0 + end + ## # Recalc the stats in the Photo for specific stats don't have to query the # database. diff --git a/binaryattraction_facebooker.rb b/binaryattraction_facebooker.rb new file mode 100755 index 0000000..ae9249c --- /dev/null +++ b/binaryattraction_facebooker.rb @@ -0,0 +1,522 @@ +#!/usr/bin/env ruby + +## +# This is my Facebook application for BinaryAttraction. It should be quick and +# easy to deal with. It has a simple interface into Facebook so that people +# can play with the application in Facebook. I do not plan on adding all of the +# features from the webapp into the Facebook application. Sorry. Get a life and +# log in like the rest of ... everyone. +# +# Supports: +# * hall of fame! +# * voting +# * adding facebook photos +# * checking stats +# * inviting your friends! +# + +# libraries used +%w(rubygems frankie active_record yaml memcache memcache_util).each do |lib| + require lib +end +# require AR models +Dir.glob(File.dirname(__FILE__) + "/app/models/*.rb") { |m| require m } + +## +# Logger setup for the Sinatra app. +# +def log + @_logger ||= Logger.new($stderr) +end + +## +# The BinaryAttraction User found by the facebook id of the currently logged +# in user. +# +def ba_user + @_user ||= User.find_by_facebook_id(session[:facebook_session].user.id) +end + +# Sinatra configuration +configure do + set :sessions, true + set :logging, log + load_facebook_config File.dirname(__FILE__) + '/config/facebooker.yml', Sinatra.env + begin + log.debug "Using #{Sinatra.env} database environment" + db_config = YAML::load_file(File.dirname(__FILE__) + '/config/database.yml')[Sinatra.env] + ActiveRecord::Base.logger = log + ActiveRecord::Base.establish_connection db_config + ActiveRecord::Base.allow_concurrency = true + rescue => exception + log.fatal "There was a problem loading the database.yml file:" + exception.backtrace.each do |msg| + log.fatal "* #{msg}" + end + exit 1 + end + config_path = File.dirname(__FILE__) + '/config/memcache.yml' + if File.file?(config_path) and File.readable?(config_path) + memcache_connection_str = YAML.load(File.read(config_path)) + else + memcache_connection_str = 'localhost:11211' + end + CACHE = MemCache.new memcache_connection_str +end + +configure :development do + log.level = Logger::DEBUG +end + +configure :production do + log.level = Logger::FATAL +end + +# facebooker helpers +before do + ensure_authenticated_to_facebook + ensure_application_is_installed_by_facebook_user + User.create(:facebook_id => session[:facebook_session].user.id) if ba_user.nil? +end + +# little welcome page, nothing much, really +post '/' do + haml :index +end + +# most oneable photos +post '/hall_of_fame' do + @top_oneness = Photo.find :all, :order => 'oneness DESC, id DESC', :limit => 10, :conditions => 'oneness > 0' + haml :hall_of_fame +end + +# go to vote on a photo +post '/vote' do + if params[:save] + @vote = Vote.new :vote => (params[:one].to_s == 'true') + @vote.user = ba_user + @vote.photo = Photo.find(params[:photo_id].to_i) rescue nil + unless @vote.save + log.info "Failed to save the vote for some reason:" + @vote.errors.each_full { |m| log.info "* #{m}" } + end + end + @photo = Photo.next_available_votable_photo ba_user + haml :vote +end + +# import photos of yourself from facebook into BA. automatically tags photos +# uploaded with your facebook id so that you can look up the stats later. since +# facebook doesn't do 'not' boolean logic, you have to find all the photos then +# subtract all of the ones already added into ba +post '/add_photos' do + @page = params[:page].to_i + per_page = 20 + if params[:save] + (0..per_page).each do |pidx| + fb_photo_id = params["photo_ids[#{pidx}]"] + break if fb_photo_id.nil? + photo = Photo.new :facebook_id => fb_photo_id, :user_id => ba_user.id + photo.email_hash = ba_user.facebook_id + photo.approved = true + unless photo.save + log.info "Failed to save the photo:" + photo.errors.each_full { |msg| log.info "* #{msg}" } + end + end + end + cache_key = "photos_of_#{ba_user.id}" + @fb_photos = Cache.get(cache_key) if @page >= 0 or params[:save] + @fb_photos ||= session[:facebook_session].get_photos nil, session[:facebook_session].user.id + if @page == 0 + ba_photos = ba_user.photos.find(:all, :select => 'facebook_id', :conditions => 'facebook_id IS NOT NULL').collect { |p| p.facebook_id.to_i } + @fb_photos.reject! { |p| ba_photos.include? p.pid.to_i } unless ba_photos.empty? + Cache.put(cache_key, @fb_photos) + end + @min_page = @page - 3 < 0 ? 0 : @page - 3 + @max_page = @fb_photos.size / per_page + @total_photo_count = @fb_photos.size + @fb_photos = @fb_photos[@page * per_page, per_page] + haml :add_photos +end + +# check your voting record and the stats on photos of you +post '/stats' do + photo_ids = Photo.find(:all, :select => 'id', :conditions => [ 'email_hash = ?', ba_user.facebook_id ]).collect { |p| p.id } rescue [] + @votes = Vote.find(:all, :conditions => "photo_id IN (#{photo_ids.join(',')})") rescue [] + haml :stats +end + +# invite your friends to BA! +post '/invite' do + haml :invite +end + +use_in_file_templates! + +__END__ + +@@ index +%style{ :type => 'text/css' } + :sass + h1 + text-align: center + .ftabs + border-bottom: 1px solid #CCCCCC + padding:0 10px 0 8px + height: 24px + #ajax_loading + display: none + float: right + margin: 7px 16px 0 0 + height: 8px + width: 28px + a + background: #F0F0F0 none repeat scroll 0 0 + border-left: 1px solid #E5E5E5 + border-right: 1px solid #CCCCCC + border-top: 1px solid #CCCCCC + color: #666666 + display: block + float: left + margin-top: 1px + padding: 4px 8px + position: relative + .first + border-left: 1px solid #CCCCCC + margin-left: 0 + .last + border-right: 1px solid #CCCCCC + a:hover + background: #fff + color: #444 + text-decoration: none + a:focus + outline: 0px + .active + background: #FFFFFF none repeat scroll 0 0 + border-bottom: 1px solid #FFFFFF + border-left: 1px solid #CCCCCC + color: #333333 + margin-bottom: -1px + margin-left: -1px + margin-top: 0 + padding-bottom: 4px + padding-top: 5px + .caption + color: #6F6F6F + font-size: 11px + font-weight: normal + margin: 3px 0 + .photo_tabs + padding: 20px 0 + h3 + height: 21px + overflow: hidden + padding: 0 10px + .pagerpro + font-size: 11px + float: right + list-style: none + margin: 0 + padding: 0 + font-weight: normal + li + display: inline + float: left + a + display: block + padding: 3px 3px 2px + a:hover + background: #3B5998 none repeat scroll 0 0 + border-bottom: 1px solid #3B5998 + border-color: #D8DFEA #D8DFEA #3B5998 + color: #FFFFFF + text-decoration: none + .current + a + color: #3B5998 + cursor: pointer + outline-style: none + text-decoration: none + a:hover + background: transparent none repeat scroll 0 0 + border-bottom: 2px solid #3B5998 + border-color: #3B5998 + color: #FFF + font-weight: bold + padding-left: 2px + padding-right: 2px + .photos_table + background: #F7F7F7 none repeat scroll 0 0 + border: 1px solid #BBBBBB + margin: 0 0 20px + overflow: hidden + padding: 0 + position: relative + width: 750px + .table_wrapper + padding: 0 5px + margin: 0 + .photos_table_cell + margin: 0 + padding: 10px 0 + text-align: center + vertical-align: middle + width: 150px + img + background: white none repeat scroll 0 0 + border: 1px solid #CCCCCC + padding: 5px + vertical-align: middle + img:hover + border: 1px solid #3B5998 + a + color: #3B5998 + cursor: pointer + outline-style: none + text-decoration: none + .top_oneness + list-style-type: none + width: 450px + margin-left: auto + margin-right: auto + li + background-color: #e9b96e + border: 1px solid #c17d11 + padding: 3px 10px + margin-bottom: 10px + p + text-align: center + #vote_container + width: 604px + margin: 0 auto 10px auto + img + display: block + margin-left: auto + margin-right: auto + #vote_controls + margin-left: auto + margin-right: auto + border: 1px solid #c17d11 + background-color: #e9b96e + width: 66px + height: 32px + img + padding: 5px + width: 22px + height: 22px + float: left + +:javascript + function run_ajax_request(url, params) + { + document.getElementById('ajax_loading').setStyle('display', 'inline'); + var a_request = new Ajax(); + a_request.responseType = Ajax.FBML; + a_request.requireLogin = true; + a_request.ondone = function(data) { + document.getElementById('ajax_loading').setStyle('display', 'none'); + document.getElementById('ba_content').setInnerFBML(data); + if(document.getElementById('error').getStyle('display') != 'none') + Animation(document.getElementById('error')).to('height', '0px').to('opacity', 0).hide().go(); + } + a_request.onerror = function() { + document.getElementById('ajax_loading').setStyle('display', 'none'); + Animation(document.getElementById('error')).to('height', 'auto').from('0px').to('width', 'auto').from('0px').to('opacity', 1).from(0).blind().show().go(); + } + a_request.post('http://facebook.binaryattraction.com/' + url, params); + } + var oldtab = ''; + function switch_tab(name) + { + var oldtab_elem = document.getElementById('tab_' + oldtab); + if(oldtab_elem) + oldtab_elem.removeClassName('active'); + oldtab = name; + document.getElementById('tab_' + oldtab).addClassName('active'); + run_ajax_request(name); + } + function switch_photos(page) + { + run_ajax_request('add_photos', {'page':page}); + } + function vote_photo(pid, one) + { + run_ajax_request('vote', {'photo_id':pid, 'save':true, 'one':one}); + } + function highlight_photo(pid) + { + var img_elem = document.getElementById('photo_' + pid); + var input_elem = document.getElementById('input_' + pid); if(!input_elem.getChecked()) + { + Animation(img_elem).to('background', '#ffff4b').from('#fff').go(); + input_elem.setChecked(true); + } else { + Animation(img_elem).to('background', '#fff').from('#ffff4b').go(); + input_elem.setChecked(false); + } + } + function add_photos() + { + run_ajax_request('add_photos', document.getElementById('add_photos_form').serialize()); + } + +%div#error{ :style => 'display: none; overflow: hidden' } + %fb:error{ :message => 'Sorry, there was a problem communicating to the binary|Attraction servers...' } +%div.ftabs + %a{ :id => 'tab_hall_of_fame', :href => '#', :onclick => "switch_tab('hall_of_fame');return false;", :class => "first" } Hall Of Fame + %a{ :id => 'tab_vote', :href => '#', :onclick => "switch_tab('vote');return false;" } Vote + %a{ :id => 'tab_add_photos', :href => '#', :onclick => "switch_tab('add_photos');return false;" } Add Photos + %a{ :id => 'tab_stats', :href => '#', :onclick => "switch_tab('stats');return false;" } Stats + %a{ :id => 'tab_invite', :href => '#', :onclick => "switch_tab('invite');return false;", :class => "last" } Invite Your Friends + %img#ajax_loading{ :alt => 'loading', :src => 'http://static.ak.fbcdn.net/images/upload_progress.gif?1:25923' } +%br{ :style => 'clear: both' } +%div#ba_content + %h1 Welcome to Binary Attraction + %p.caption{ :style => 'text-align: center' } Check out our home page | Produced by PenguinCoder + %p Check out all of the neat stuff we have lying around. Vote on some photos, upload some of yours, and check your stats. Beat your friends' oneness! + +@@ hall_of_fame +%h1 Top oneness +%ol.top_oneness + - @top_oneness.each_with_index do |p, index| + %li + - if p.facebook_id.to_i == 0 + %img{ :style => 'display: block; margin: 0px auto', :src => "http://binaryattraction.com/photos/#{p.id}/thumbnail?width=300&height=300" } + - else + %fb:photo{ :pid => p.facebook_id, :size => 'album' } + %p{ :style => 'text-align: center' }== Oneness: #{p.oneness}% Votes 0: #{p.zero_votes} Votes 1: #{p.one_votes} Total Votes: #{p.votes_count} + +@@ vote +- if @photo.nil? + %fb:explanation{ :message => 'You have run out of photos to vote! Try adding some more' } +- else + %div#vote_container + - if @photo.facebook_id + %fb:photo{ :pid => @photo.facebook_id, :size => 'normal' } + - else + %img{ :src => "http://binaryattraction.com#{@photo.pathname}", :alt => @photo.filename, :width => @photo.width, :height => @photo.height } + %div#vote_controls + %a{ :href => '#', :onclick => "vote_photo(#{@photo.id}, false); return false;", :title => '0-able' } + %img{ :src => 'http://binaryattraction.com/images/0.png' } + %a{ :href => '#', :onclick => "vote_photo(#{@photo.id}, true); return false;", :title => '1-able' } + %img{ :src => 'http://binaryattraction.com/images/1.png' } + +@@ add_photos +%div.photo_tabs + %h3 + %ul.pagerpro + - if @page > 0 + %li + %a{ :href => '#', :onclick => "switch_photos(#{@page - 1});return false;" } Prev + - @min_page.upto(@max_page) do |pnum| + - if pnum == @page + %li.current + %a{ :href => '#', :onclick => 'return false;' }= pnum + 1 + - else + %li + %a{ :href => '#', :onclick => "switch_photos(#{pnum});return false;" }= pnum + 1 + - if @page < @max_page + %li + %a{ :href => '#', :onclick => "switch_photos(#{@page + 1});return false;" } Next + %span== Photos of #{session[:facebook_session].user.first_name} + %span.caption== #{@total_photo_count} photos | Pick some photos | Try to choose images of just yourself + %form{ :id => 'add_photos_form' } + %input{ :type => 'hidden', :name => 'save', :value => 'true' } + %div.photos_table + %div.table_wrapper + %table + %tbody + - @fb_photos.each_slice(5) do |slice| + %tr + - slice.each do |photo| + %td.photos_table_cell + %fb:photo{ :pid => photo.pid, :size => 'small', :id => "photo_#{photo.pid}", :onclick => "highlight_photo('#{photo.pid}'); return false;" } + %input{ :style => 'display: none', :type => 'checkbox', :name => "photo_ids[]", :id => "input_#{photo.pid}", :value => photo.pid } + %input{ :type => 'button', :class => 'inputbutton', :value => 'Add Photos', :onclick => "add_photos(); return false;" } + +@@ stats +%style{ :type => 'text/css' } + :sass + .poll_results + width: 420px + h2 + background-color: #6D84B4 + color: #FFF + font-size: 11px + padding: 5px + margin: 0 + .poll_answers + background: #FFF none repeat scroll 0 0 + border-color: #CCCCCC + border-style: solid + border-width: 0 1px 1px + padding: 5px + p + margin: 0 + font-weight: bold + table + margin: 10px 0 10px 0 + .label + padding-right: 10px + text-align: right + font-size: 11px + .bar + background-attachment: scroll + background-color: #3B5998 + background-image: none + background-position: 0 0 + background-repeat: repeat + float: left + height: 18px + margin-right: 5px + color: #FFF + text-align: left + font-weight: bold + font-size: 13px + padding: 1px 1px 1px 3px + +%div.poll_results + %h2== Your oneness from #{@votes.size} votes + %div.poll_answers + %table + %tbody + %tr + %td + %p.label Oneness + %td + - oneness = "%.1f" % (@votes.select { |v| v.one? }.size.to_f / @votes.size.to_f * 100.0) + %div.bar{ :style => "width: #{240 * oneness.to_i / 100}px" }== #{oneness}% + %tr + %td + %p.label Votes 1 + %td= @votes.select { |v| v.one? }.size + %tr + %td + %p.label Votes 0 + %td= @votes.select { |v| v.zero? }.size +%br +%div.poll_results + %h2== Your voting results for #{ba_user.votes.size} photos + %div.poll_answers + %table + %tbody + %tr + %td + %p.label Oneness + %td + - oneness = "%.1f" % (ba_user.votes.select { |v| v.one? }.size.to_f / ba_user.votes.size.to_f * 100.0) + %div.bar{ :style => "width: #{240 * oneness.to_i / 100}px" }== #{oneness}% + %tr + %td + %p.label Votes 1 + %td= ba_user.votes.select { |v| v.one? }.size + %tr + %td + %p.label Votes 0 + %td= ba_user.votes.select { |v| v.zero? }.size + +@@ invite +%fb:request-form{ :type => 'Binary Attraction', :content => "Your friends think you should check out your oneness at ", :action => 'http://apps.binaryattraction.com/binaryattraction', :invite => true, :method => 'POST' } + %fb:multi-friend-selector{ :actiontext => "Invite your friends to vote on ones (and not ones)", :showborder => true, :exclude_ids => session[:facebook_session].user.friends_with_this_app.map(&:id).join(","), :bypass => "cancel" } diff --git a/config/init.rb b/config/init.rb index 451eff5..cdc953f 100644 --- a/config/init.rb +++ b/config/init.rb @@ -2,7 +2,10 @@ Gem.clear_paths Gem.path.unshift(Merb.root / "gems") $LOAD_PATH.unshift(Merb.root / "lib") -dependencies 'haml', 'sass', 'merb_helpers', 'merb_has_flash', 'digest/sha1', 'merb-mailer', 'recaptcha' +dependencies 'haml', 'merb_helpers', 'merb_has_flash', 'merb-mailer' +require 'digest/sha1' +require 'sass' +require 'recaptcha' require 'merb_exceptions' require 'RMagick' require 'memcache' diff --git a/gems/facebooker-0.9.9.gem b/gems/facebooker-0.9.9.gem new file mode 100644 index 0000000..864bbb6 Binary files /dev/null and b/gems/facebooker-0.9.9.gem differ diff --git a/gems/frankie-0.2.5.gem b/gems/frankie-0.2.5.gem new file mode 100644 index 0000000..c2ec2c8 Binary files /dev/null and b/gems/frankie-0.2.5.gem differ diff --git a/gems/sinatra-0.3.2.gem b/gems/sinatra-0.3.2.gem new file mode 100644 index 0000000..c573c7f Binary files /dev/null and b/gems/sinatra-0.3.2.gem differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..2c3722a Binary files /dev/null and b/public/favicon.ico differ diff --git a/schema/schema.rb b/schema/schema.rb index c18045e..a415458 100644 --- a/schema/schema.rb +++ b/schema/schema.rb @@ -9,15 +9,15 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 8) do +ActiveRecord::Schema.define(:version => 9) do create_table "photo_favorites", :force => true do |t| t.integer "photo_id" t.integer "user_id" end - add_index "photo_favorites", ["user_id"], :name => "index_photo_favorites_on_user_id" add_index "photo_favorites", ["photo_id"], :name => "index_photo_favorites_on_photo_id" + add_index "photo_favorites", ["user_id"], :name => "index_photo_favorites_on_user_id" create_table "photo_flags", :force => true do |t| t.integer "user_id" @@ -25,8 +25,8 @@ ActiveRecord::Schema.define(:version => 8) do t.string "session_id" end - add_index "photo_flags", ["photo_id"], :name => "index_photo_flags_on_photo_id" add_index "photo_flags", ["user_id"], :name => "index_photo_flags_on_user_id" + add_index "photo_flags", ["photo_id"], :name => "index_photo_flags_on_photo_id" create_table "photos", :force => true do |t| t.string "filename" @@ -36,19 +36,21 @@ ActiveRecord::Schema.define(:version => 8) do t.integer "height" t.integer "user_id" t.datetime "created_at" - t.boolean "approved", :default => false - t.integer "votes_count", :default => 0 - t.integer "one_votes", :default => 0 - t.integer "zero_votes", :default => 0 + t.boolean "approved", :default => false + t.integer "votes_count", :default => 0 + t.integer "one_votes", :default => 0 + t.integer "zero_votes", :default => 0 t.float "oneness" - t.integer "photo_flags_count", :default => 0 + t.integer "photo_flags_count", :default => 0 + t.integer "facebook_id", :limit => 8 end - add_index "photos", ["approved"], :name => "index_photos_on_approved" - add_index "photos", ["oneness"], :name => "index_photos_on_oneness" - add_index "photos", ["votes_count"], :name => "index_photos_on_votes_count" - add_index "photos", ["user_id"], :name => "index_photos_on_user_id" add_index "photos", ["email_hash"], :name => "index_photos_on_email_hash" + add_index "photos", ["user_id"], :name => "index_photos_on_user_id" + add_index "photos", ["votes_count"], :name => "index_photos_on_votes_count" + add_index "photos", ["oneness"], :name => "index_photos_on_oneness" + add_index "photos", ["approved"], :name => "index_photos_on_approved" + add_index "photos", ["facebook_id"], :name => "index_photos_on_facebook_id" create_table "sessions", :force => true do |t| t.string "session_id" @@ -63,9 +65,11 @@ ActiveRecord::Schema.define(:version => 8) do t.string "auth_token" t.boolean "administrator" t.datetime "created_at" + t.integer "facebook_id", :limit => 8 end add_index "users", ["user_name"], :name => "index_users_on_user_name" + add_index "users", ["facebook_id"], :name => "index_users_on_facebook_id" create_table "votes", :force => true do |t| t.integer "photo_id" @@ -74,8 +78,8 @@ ActiveRecord::Schema.define(:version => 8) do t.boolean "vote" end - add_index "votes", ["session_id"], :name => "index_votes_on_session_id" - add_index "votes", ["user_id"], :name => "index_votes_on_user_id" add_index "votes", ["photo_id"], :name => "index_votes_on_photo_id" + add_index "votes", ["user_id"], :name => "index_votes_on_user_id" + add_index "votes", ["session_id"], :name => "index_votes_on_session_id" end