8784: Fix test for latest firefox.
[arvados.git] / services / api / db / migrate / 20150317132720_add_username_to_users.rb
1 require 'has_uuid'
2 require 'kind_and_etag'
3
4 class AddUsernameToUsers < ActiveRecord::Migration
5   include CurrentApiClient
6
7   SEARCH_INDEX_COLUMNS =
8     ["uuid", "owner_uuid", "modified_by_client_uuid",
9      "modified_by_user_uuid", "email", "first_name", "last_name",
10      "identity_url", "default_owner_uuid"]
11
12   class ArvadosModel < ActiveRecord::Base
13     self.abstract_class = true
14     extend HasUuid::ClassMethods
15     include CurrentApiClient
16     include KindAndEtag
17     before_create do |record|
18       record.uuid ||= record.class.generate_uuid
19       record.owner_uuid ||= system_user_uuid
20     end
21     serialize :properties, Hash
22
23     def self.to_s
24       # Clean up the name of the stub model class so we generate correct UUIDs.
25       super.rpartition("::").last
26     end
27   end
28
29   class Log < ArvadosModel
30     def self.log_for(thing, age="old")
31       { "#{age}_etag" => thing.etag,
32         "#{age}_attributes" => thing.attributes,
33       }
34     end
35
36     def self.log_create(thing)
37       new_log("create", thing, log_for(thing, "new"))
38     end
39
40     def self.log_update(thing, start_state)
41       new_log("update", thing, start_state.merge(log_for(thing, "new")))
42     end
43
44     def self.log_destroy(thing)
45       new_log("destroy", thing, log_for(thing, "old"))
46     end
47
48     private
49
50     def self.new_log(event_type, thing, properties)
51       create!(event_type: event_type,
52               event_at: Time.now,
53               object_uuid: thing.uuid,
54               object_owner_uuid: thing.owner_uuid,
55               properties: properties)
56     end
57   end
58
59   class Link < ArvadosModel
60   end
61
62   class User < ArvadosModel
63   end
64
65   def sanitize_username(username)
66     username.
67       sub(/^[^A-Za-z]+/, "").
68       gsub(/[^A-Za-z0-9]/, "")
69   end
70
71   def usernames_wishlist(user)
72     usernames = Hash.new(0)
73     usernames[user.email.split("@", 2).first] += 1
74     Link.
75        where(tail_uuid: user.uuid, link_class: "permission", name: "can_login").
76        find_each do |login_perm|
77       username = login_perm.properties["username"]
78       usernames[username] += 2 if (username and not username.empty?)
79     end
80     usernames.keys.
81       sort_by { |n| -usernames[n] }.
82       map { |n| sanitize_username(n) }.
83       reject(&:empty?)
84   end
85
86   def increment_username(username)
87     @username_suffixes[username] += 1
88     "%s%i" % [username, @username_suffixes[username]]
89   end
90
91   def each_wanted_username(user)
92     usernames = usernames_wishlist(user)
93     usernames.each { |n| yield n }
94     base_username = usernames.first || "arvadosuser"
95     loop { yield increment_username(base_username) }
96   end
97
98   def recreate_search_index(columns)
99     remove_index :users, name: "users_search_index"
100     add_index :users, columns, name: "users_search_index"
101   end
102
103   def up
104     @username_suffixes = Hash.new(1)
105     add_column :users, :username, :string, null: true
106     add_index :users, :username, unique: true
107     recreate_search_index(SEARCH_INDEX_COLUMNS + ["username"])
108
109     [Link, Log, User].each { |m| m.reset_column_information }
110     User.validates(:username, uniqueness: true, allow_nil: true)
111     User.where(is_active: true).order(created_at: :asc).find_each do |user|
112       start_log = Log.log_for(user)
113       each_wanted_username(user) do |username|
114         user.username = username
115         break if user.valid?
116       end
117       user.save!
118       Log.log_update(user, start_log)
119     end
120   end
121
122   def down
123     remove_index :users, :username
124     recreate_search_index(SEARCH_INDEX_COLUMNS)
125     remove_column :users, :username
126   end
127 end