twitter-auth-with-mongo-mapper 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.markdown +11 -0
- data/README.markdown +5 -0
- data/Rakefile +31 -0
- data/VERSION.yml +4 -0
- data/app/controllers/sessions_controller.rb +81 -0
- data/app/models/twitter_auth/basic_user.rb +64 -0
- data/app/models/twitter_auth/generic_user.rb +135 -0
- data/app/models/twitter_auth/oauth_user.rb +50 -0
- data/app/views/sessions/_login_form.html.erb +17 -0
- data/app/views/sessions/new.html.erb +5 -0
- data/config/routes.rb +6 -0
- data/generators/twitter_auth/USAGE +12 -0
- data/generators/twitter_auth/templates/migration.rb +49 -0
- data/generators/twitter_auth/templates/twitter_auth.yml +47 -0
- data/generators/twitter_auth/templates/user.rb +5 -0
- data/generators/twitter_auth/twitter_auth_generator.rb +34 -0
- data/lib/twitter_auth.rb +100 -0
- data/lib/twitter_auth/controller_extensions.rb +82 -0
- data/lib/twitter_auth/cryptify.rb +31 -0
- data/lib/twitter_auth/dispatcher/basic.rb +46 -0
- data/lib/twitter_auth/dispatcher/oauth.rb +27 -0
- data/lib/twitter_auth/dispatcher/shared.rb +40 -0
- data/rails/init.rb +8 -0
- data/spec/controllers/controller_extensions_spec.rb +162 -0
- data/spec/controllers/sessions_controller_spec.rb +221 -0
- data/spec/fixtures/config/twitter_auth.yml +17 -0
- data/spec/fixtures/factories.rb +20 -0
- data/spec/fixtures/fakeweb.rb +18 -0
- data/spec/fixtures/twitter.rb +5 -0
- data/spec/models/twitter_auth/basic_user_spec.rb +138 -0
- data/spec/models/twitter_auth/generic_user_spec.rb +146 -0
- data/spec/models/twitter_auth/oauth_user_spec.rb +100 -0
- data/spec/schema.rb +42 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/twitter_auth/cryptify_spec.rb +51 -0
- data/spec/twitter_auth/dispatcher/basic_spec.rb +83 -0
- data/spec/twitter_auth/dispatcher/oauth_spec.rb +72 -0
- data/spec/twitter_auth/dispatcher/shared_spec.rb +26 -0
- data/spec/twitter_auth_spec.rb +160 -0
- metadata +151 -0
data/CHANGELOG.markdown
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Change Log
|
2
|
+
==========
|
3
|
+
|
4
|
+
== 0.1.22 - June 19, 2009
|
5
|
+
|
6
|
+
- Merged in @pengwynn's OAuth 1.0a support patch. Thanks!
|
7
|
+
|
8
|
+
== 0.1.19 - April 23, 2009
|
9
|
+
|
10
|
+
- Added this changelog to keep track of additions to TwitterAuth
|
11
|
+
- All authentication is now keyed via `id` instead of `screen_name`
|
data/README.markdown
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
TwitterAuth is an awesome gem written by mbleigh, which you can check out [here](http://github.com/mbleigh/twitter-auth/).
|
2
|
+
|
3
|
+
MongoMapper is an awesome gem written by jnunemaker, which you can check out [here](http://github.com/jnunemaker).
|
4
|
+
|
5
|
+
This was a proof of concept for using them together. Really very little needed to be changed.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc 'Default: run specs.'
|
5
|
+
task :default => :spec
|
6
|
+
|
7
|
+
desc 'Run the specs'
|
8
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
9
|
+
t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |s|
|
16
|
+
s.name = "twitter-auth-with-mongo-mapper"
|
17
|
+
s.summary = "TwitterAuth is a Rails plugin gem that provides Single Sign-On capabilities for Rails applications via Twitter."
|
18
|
+
s.email = "michael@intridea.com"
|
19
|
+
s.homepage = "http://github.com/mbleigh/twitter-auth"
|
20
|
+
s.description = "TwitterAuth is a Rails plugin gem that provides Single Sign-On capabilities for Rails applications via Twitter. Both OAuth and HTTP Basic are supported."
|
21
|
+
s.files = FileList["[A-Z]*", "{bin,generators,lib,spec,config,app,rails}/**/*"] - FileList["**/*.log"]
|
22
|
+
|
23
|
+
s.authors = ["Michael Bleigh"]
|
24
|
+
s.add_dependency('oauth', '>= 0.3.1')
|
25
|
+
s.add_dependency('ezcrypto', '>= 0.7.2')
|
26
|
+
s.rubyforge_project = 'twitter-auth'
|
27
|
+
end
|
28
|
+
rescue LoadError
|
29
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
30
|
+
end
|
31
|
+
|
data/VERSION.yml
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
class SessionsController < ApplicationController
|
2
|
+
#unloadable
|
3
|
+
|
4
|
+
def new
|
5
|
+
if TwitterAuth.oauth?
|
6
|
+
oauth_callback = request.protocol + request.host_with_port + '/oauth_callback'
|
7
|
+
@request_token = TwitterAuth.consumer.get_request_token({:oauth_callback=>oauth_callback})
|
8
|
+
session[:request_token] = @request_token.token
|
9
|
+
session[:request_token_secret] = @request_token.secret
|
10
|
+
|
11
|
+
url = @request_token.authorize_url
|
12
|
+
url << "&oauth_callback=#{CGI.escape(TwitterAuth.oauth_callback)}" if TwitterAuth.oauth_callback?
|
13
|
+
redirect_to url
|
14
|
+
else
|
15
|
+
# we don't have to do anything, it's just a simple form for HTTP basic!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
logout_keeping_session!
|
21
|
+
if user = User.authenticate(params[:login], params[:password])
|
22
|
+
self.current_user = user
|
23
|
+
authentication_succeeded and return
|
24
|
+
else
|
25
|
+
authentication_failed('Unable to verify your credentials through Twitter. Please try again.', '/login') and return
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def oauth_callback
|
30
|
+
unless session[:request_token] && session[:request_token_secret]
|
31
|
+
authentication_failed('No authentication information was found in the session. Please try again.') and return
|
32
|
+
end
|
33
|
+
|
34
|
+
unless params[:oauth_token].blank? || session[:request_token] == params[:oauth_token]
|
35
|
+
authentication_failed('Authentication information does not match session information. Please try again.') and return
|
36
|
+
end
|
37
|
+
|
38
|
+
@request_token = OAuth::RequestToken.new(TwitterAuth.consumer, session[:request_token], session[:request_token_secret])
|
39
|
+
|
40
|
+
oauth_verifier = params["oauth_verifier"]
|
41
|
+
@access_token = @request_token.get_access_token(:oauth_verifier => oauth_verifier)
|
42
|
+
|
43
|
+
# The request token has been invalidated
|
44
|
+
# so we nullify it in the session.
|
45
|
+
session[:request_token] = nil
|
46
|
+
session[:request_token_secret] = nil
|
47
|
+
|
48
|
+
@user = User.identify_or_create_from_access_token(@access_token)
|
49
|
+
|
50
|
+
if @user.onboard_status == 0
|
51
|
+
authentication_failed("You need to be vowched for before you can sign up.")
|
52
|
+
elsif @user.onboard_status == 1
|
53
|
+
@user.update_attributes(:onboard_status => 2)
|
54
|
+
session[:user_id] = @user.id
|
55
|
+
cookies[:remember_token] = { :value => @user.remember_me, :expires => 1.year.from_now}
|
56
|
+
onboard_user
|
57
|
+
else
|
58
|
+
session[:user_id] = @user.id
|
59
|
+
cookies[:remember_token] = { :value => @user.remember_me, :expires => 1.year.from_now}
|
60
|
+
if @user.onboard_status == 2
|
61
|
+
#onboard_user
|
62
|
+
authentication_succeeded
|
63
|
+
else
|
64
|
+
authentication_succeeded
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
rescue Net::HTTPServerException => e
|
69
|
+
case e.message
|
70
|
+
when '401 "Unauthorized"'
|
71
|
+
authentication_failed('This authentication request is no longer valid. Please try again.') and return
|
72
|
+
else
|
73
|
+
authentication_failed('There was a problem trying to authenticate you. Please try again.') and return
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def destroy
|
78
|
+
logout_keeping_session!
|
79
|
+
redirect_back_or_default('/')
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module TwitterAuth
|
4
|
+
module BasicUser
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
attr_accessor :crypted_password, :salt
|
8
|
+
end
|
9
|
+
|
10
|
+
base.extend TwitterAuth::BasicUser::ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def verify_credentials(login, password)
|
15
|
+
response = TwitterAuth.net.start { |http|
|
16
|
+
request = Net::HTTP::Get.new('/account/verify_credentials.json')
|
17
|
+
request.basic_auth login, password
|
18
|
+
http.request(request)
|
19
|
+
}
|
20
|
+
|
21
|
+
if response.code == '200'
|
22
|
+
JSON.parse(response.body)
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def authenticate(login, password)
|
29
|
+
if twitter_hash = verify_credentials(login, password)
|
30
|
+
user = identify_or_create_from_twitter_hash_and_password(twitter_hash, password)
|
31
|
+
user
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def identify_or_create_from_twitter_hash_and_password(twitter_hash, password)
|
38
|
+
if user = User.find_by_twitter_id(twitter_hash['id'].to_s)
|
39
|
+
user.login = twitter_hash['screen_name']
|
40
|
+
user.assign_twitter_attributes(twitter_hash)
|
41
|
+
user.password = password
|
42
|
+
user.save
|
43
|
+
user
|
44
|
+
else
|
45
|
+
user = User.new_from_twitter_hash(twitter_hash)
|
46
|
+
user.password = password
|
47
|
+
user.save
|
48
|
+
user
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def password=(new_password)
|
54
|
+
encrypted = TwitterAuth::Cryptify.encrypt(new_password)
|
55
|
+
self.crypted_password = encrypted[:encrypted_data]
|
56
|
+
self.salt = encrypted[:salt]
|
57
|
+
end
|
58
|
+
|
59
|
+
def password
|
60
|
+
TwitterAuth::Cryptify.decrypt(self.crypted_password, self.salt)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module TwitterAuth
|
2
|
+
class GenericUser
|
3
|
+
include MongoMapper::Document
|
4
|
+
|
5
|
+
attr_accessor :twitter_id, :remember_token, :remember_token_expires_at
|
6
|
+
|
7
|
+
key :access_secret, String
|
8
|
+
key :access_token, String
|
9
|
+
key :created_at, String
|
10
|
+
key :crypted_password, String
|
11
|
+
key :description, String
|
12
|
+
key :favourites_count, String
|
13
|
+
key :followers_count, String
|
14
|
+
key :friends_count, String
|
15
|
+
key :location, String
|
16
|
+
key :login, String
|
17
|
+
key :name, String
|
18
|
+
key :profile_background_color, String
|
19
|
+
key :profile_background_image_url, String
|
20
|
+
key :profile_background_tile, String
|
21
|
+
key :profile_image_url, String
|
22
|
+
key :profile_link_color, String
|
23
|
+
key :profile_sidebar_border_color, String
|
24
|
+
key :profile_sidebar_fill_color, String
|
25
|
+
key :profile_text_color, String
|
26
|
+
key :protected, String
|
27
|
+
key :remember_token, String
|
28
|
+
key :remember_token_expires_at, String
|
29
|
+
key :salt, String
|
30
|
+
key :statuses_count, String
|
31
|
+
key :time_zone, String
|
32
|
+
key :twitter_id, String
|
33
|
+
key :updated_at, String
|
34
|
+
key :url, String
|
35
|
+
key :utc_offset, String
|
36
|
+
|
37
|
+
TWITTER_ATTRIBUTES = [
|
38
|
+
:name,
|
39
|
+
:location,
|
40
|
+
:description,
|
41
|
+
:profile_image_url,
|
42
|
+
:url,
|
43
|
+
:protected,
|
44
|
+
:profile_background_color,
|
45
|
+
:profile_sidebar_fill_color,
|
46
|
+
:profile_link_color,
|
47
|
+
:profile_sidebar_border_color,
|
48
|
+
:profile_text_color,
|
49
|
+
:profile_background_image_url,
|
50
|
+
:profile_background_tile,
|
51
|
+
:friends_count,
|
52
|
+
:statuses_count,
|
53
|
+
:followers_count,
|
54
|
+
:favourites_count,
|
55
|
+
:time_zone,
|
56
|
+
:utc_offset
|
57
|
+
]
|
58
|
+
|
59
|
+
with_options :if => :utilize_default_validations do |v|
|
60
|
+
# v.validates_presence_of :login, :twitter_id
|
61
|
+
# v.validates_format_of :login, :with => /\A[a-z0-9_]+\z/i
|
62
|
+
# v.validates_length_of :login, :in => 1..15
|
63
|
+
# v.validates_uniqueness_of :login, :case_sensitive => false
|
64
|
+
# v.validates_uniqueness_of :twitter_id, :message => "ID has already been taken."
|
65
|
+
# v.validates_uniqueness_of :remember_token, :allow_blank => true
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.table_name; 'users' end
|
69
|
+
|
70
|
+
def self.new_from_twitter_hash(hash)
|
71
|
+
raise ArgumentError, 'Invalid hash: must include screen_name.' unless hash.key?('screen_name')
|
72
|
+
|
73
|
+
raise ArgumentError, 'Invalid hash: must include id.' unless hash.key?('id')
|
74
|
+
|
75
|
+
user = User.new
|
76
|
+
user.twitter_id = hash['id'].to_s
|
77
|
+
user.login = hash['screen_name']
|
78
|
+
|
79
|
+
TWITTER_ATTRIBUTES.each do |att|
|
80
|
+
user.send("#{att}=", hash[att.to_s]) if user.respond_to?("#{att}=")
|
81
|
+
end
|
82
|
+
|
83
|
+
user
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.from_remember_token(token)
|
87
|
+
first(:conditions => ["remember_token = ? AND remember_token_expires_at > ?", token, Time.now])
|
88
|
+
end
|
89
|
+
|
90
|
+
def assign_twitter_attributes(hash)
|
91
|
+
TWITTER_ATTRIBUTES.each do |att|
|
92
|
+
send("#{att}=", hash[att.to_s]) if respond_to?("#{att}=")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_twitter_attributes(hash)
|
97
|
+
assign_twitter_attributes(hash)
|
98
|
+
save
|
99
|
+
end
|
100
|
+
|
101
|
+
if TwitterAuth.oauth?
|
102
|
+
include TwitterAuth::OauthUser
|
103
|
+
else
|
104
|
+
include TwitterAuth::BasicUser
|
105
|
+
end
|
106
|
+
|
107
|
+
def utilize_default_validations
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
def twitter
|
112
|
+
if TwitterAuth.oauth?
|
113
|
+
TwitterAuth::Dispatcher::Oauth.new(self)
|
114
|
+
else
|
115
|
+
TwitterAuth::Dispatcher::Basic.new(self)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def remember_me
|
120
|
+
return false unless respond_to?(:remember_token)
|
121
|
+
|
122
|
+
self.remember_token = ActiveSupport::SecureRandom.hex(10)
|
123
|
+
self.remember_token_expires_at = Time.now + TwitterAuth.remember_for.days
|
124
|
+
|
125
|
+
save
|
126
|
+
|
127
|
+
{:value => self.remember_token, :expires => self.remember_token_expires_at}
|
128
|
+
end
|
129
|
+
|
130
|
+
def forget_me
|
131
|
+
self.remember_token = self.remember_token_expires_at = nil
|
132
|
+
self.save
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module TwitterAuth
|
2
|
+
module OauthUser
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
attr_accessor :access_token, :access_secret
|
6
|
+
end
|
7
|
+
|
8
|
+
base.extend TwitterAuth::OauthUser::ClassMethods
|
9
|
+
base.extend TwitterAuth::Dispatcher::Shared
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def identify_or_create_from_access_token(token, secret=nil)
|
14
|
+
raise ArgumentError, 'Must authenticate with an OAuth::AccessToken or the string access token and secret.' unless (token && secret) || token.is_a?(OAuth::AccessToken)
|
15
|
+
|
16
|
+
token = OAuth::AccessToken.new(TwitterAuth.consumer, token, secret) unless token.is_a?(OAuth::AccessToken)
|
17
|
+
|
18
|
+
response = token.get(TwitterAuth.path_prefix + '/account/verify_credentials.json')
|
19
|
+
user_info = handle_response(response)
|
20
|
+
|
21
|
+
if user = User.find_by_login(user_info['screen_name'].to_s)
|
22
|
+
if user.onboard_status == 1
|
23
|
+
user.onboard_status = 2
|
24
|
+
end
|
25
|
+
user.login = user_info['screen_name']
|
26
|
+
user.assign_twitter_attributes(user_info)
|
27
|
+
user.access_token = token.token
|
28
|
+
user.access_secret = token.secret
|
29
|
+
user.save
|
30
|
+
user
|
31
|
+
else
|
32
|
+
User.create_from_twitter_hash_and_token(user_info, token)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_from_twitter_hash_and_token(user_info, access_token)
|
37
|
+
user = User.new_from_twitter_hash(user_info)
|
38
|
+
user.access_token = access_token.token
|
39
|
+
user.access_secret = access_token.secret
|
40
|
+
user.onboard_status = 0
|
41
|
+
user.save
|
42
|
+
user
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def token
|
47
|
+
OAuth::AccessToken.new(TwitterAuth.consumer, access_token, access_secret)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% form_tag session_path, :id => 'login_form' do %>
|
2
|
+
<div class='field'>
|
3
|
+
<label for='login'>Twitter Username:</label>
|
4
|
+
<%= text_field_tag 'login', nil, :class => 'text_field' %>
|
5
|
+
</div>
|
6
|
+
<div class='field'>
|
7
|
+
<label for='password'>Password:</label>
|
8
|
+
<%= password_field_tag 'password', nil, :class => 'password_field' %>
|
9
|
+
</div>
|
10
|
+
<!--<div class='checkbox-field'>
|
11
|
+
<%= check_box_tag 'remember_me' %> <label for='remember_me'>Keep Me Logged In</label>
|
12
|
+
</div>-->
|
13
|
+
<div class='field submit'>
|
14
|
+
<%= submit_tag 'Log In', :class => 'submit' %>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
17
|
+
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<h1>Log In Via Twitter</h1>
|
2
|
+
|
3
|
+
<p>This application utilizes your Twitter username and password for authentication; you do not have to create a separate account here. To log in, just enter your Twitter credentials in the form below.</p>
|
4
|
+
|
5
|
+
<%= render :partial => 'login_form' %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
ActionController::Routing::Routes.draw do |map|
|
2
|
+
map.login '/login', :controller => 'sessions', :action => 'new'
|
3
|
+
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
|
4
|
+
map.resource :session
|
5
|
+
map.oauth_callback '/oauth_callback', :controller => 'sessions', :action => 'oauth_callback'
|
6
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
TwitterAuth Generator
|
2
|
+
=====================
|
3
|
+
|
4
|
+
The TwitterAuth generator allows you to generate the components necessary to implement Twitter as a Single Sign-On provider for your site.
|
5
|
+
|
6
|
+
To run it, you simply need to call it:
|
7
|
+
|
8
|
+
script/generate twitter_auth
|
9
|
+
|
10
|
+
This will generate the migration necessary for the users table as well as generate a User model that extends the appropriate TwitterAuth model template and a config/twitter.yml that allows you to set your OAuth consumer key and secret.
|
11
|
+
|
12
|
+
By default, TwitterAuth uses OAuth as its authentication strategy. If you wish to use HTTP Basic you can pass in the --basic option.
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class TwitterAuthMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :users do |t|
|
4
|
+
t.string :twitter_id
|
5
|
+
t.string :login
|
6
|
+
<% if options[:oauth] -%>
|
7
|
+
t.string :access_token
|
8
|
+
t.string :access_secret
|
9
|
+
<% elsif options[:basic] -%>
|
10
|
+
t.binary :crypted_password
|
11
|
+
t.string :salt
|
12
|
+
<% end -%>
|
13
|
+
|
14
|
+
t.string :remember_token
|
15
|
+
t.datetime :remember_token_expires_at
|
16
|
+
|
17
|
+
# This information is automatically kept
|
18
|
+
# in-sync at each login of the user. You
|
19
|
+
# may remove any/all of these columns.
|
20
|
+
t.string :name
|
21
|
+
t.string :location
|
22
|
+
t.string :description
|
23
|
+
t.string :profile_image_url
|
24
|
+
t.string :url
|
25
|
+
t.boolean :protected
|
26
|
+
t.string :profile_background_color
|
27
|
+
t.string :profile_sidebar_fill_color
|
28
|
+
t.string :profile_link_color
|
29
|
+
t.string :profile_sidebar_border_color
|
30
|
+
t.string :profile_text_color
|
31
|
+
t.string :profile_background_image_url
|
32
|
+
t.boolean :profile_background_tile
|
33
|
+
t.integer :friends_count
|
34
|
+
t.integer :statuses_count
|
35
|
+
t.integer :followers_count
|
36
|
+
t.integer :favourites_count
|
37
|
+
|
38
|
+
# Probably don't need both, but they're here.
|
39
|
+
t.integer :utc_offset
|
40
|
+
t.string :time_zone
|
41
|
+
|
42
|
+
t.timestamps
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.down
|
47
|
+
drop_table :users
|
48
|
+
end
|
49
|
+
end
|