Социальные приложения и графические базы данных сочетаются как арахисовое масло и желе. Я собираюсь рассказать вам о том, как создать приложение, которое подключается к Facebook, тянет ваших друзей, любит данные и визуализирует их. Я планирую сделать видео со мной, кодирующим его по одной строке за раз, но сейчас давайте просто сосредоточимся на основных элементах.
Приложение будет иметь два основных компонента:
- Веб-сервис, который обрабатывает аутентификацию и отображение друзей, лайков и так далее.
- Фоновая служба заданий, которая импортирует данные из Facebook.
Мы будем развертывать это приложение на Heroku и использовать дополнения RedisToGo и Neo4j .
Давайте начнем с клонирования приложения и его создания в Heroku.
git clone [email protected]:maxdemarzi/neosocial.git heroku apps:create heroku addons:add neo4j heroku addons:add redistogo
Поскольку мы подключаемся к Facebook, вам необходимо получить идентификатор приложения Facebook и секретный ключ по адресу https://developers.facebook.com/apps .
Включите «Веб-сайт с логином Facebook» и установите его в своем домене http://xxxxxxx.herokuapp.com.
Придумайте секрет сеанса (подойдет любой длинный текст или предложение) и добавьте его и ваши параметры Facebook в ваше приложение.
heroku config:add SESSION_SECRET=<your session secret> heroku config:add FACEBOOK_APP_ID=<your facebook app id> heroku config:add FACEBOOK_SECRET=<your facebook secret>
Теперь нам нужно просто развернуть наше приложение в Heroku и увеличить количество рабочих до 1.
git push heroku master heroku ps:scale worker=1
Если вы перейдете на свой домен xxxxx.herokuapp.com, вы должны увидеть:
Так что же происходит, когда пользователь нажимает «Войти через Facebook»? Они отправляются в Facebook для аутентификации через Oauth, и, если они одобрят, создается объект User, и они отправляются на страницу своего профиля.
['get', 'post'].each do |method| send(method, "/auth/:provider/callback") do user = User.create_with_omniauth(env['omniauth.auth']) session[:uid] = user.uid redirect to(session[:redirect_url] || "/user/#{session[:uid]}") session[:redirect_url] = nil end end
Давайте посмотрим на метод create_with_omniauth. Он создает уникальный узел с использованием идентификатора Facebook, токена и значений, которые мы получили при аутентификации, и возвращает нового пользователя.
def self.create_with_omniauth(auth) values = {"name" => auth.info.name, "image_url" => auth.info.image, "location" => auth.info.location, "uid" => auth.uid, "token" => auth.credentials.token} node = $neo_server.create_unique_node("user_index", "uid", auth.uid, values) Sidekiq::Client.enqueue(Job::ImportFacebookProfile, auth.uid) User.new(node) end
Узел — это просто хеш, и мы могли бы построить все это приложение, используя простые хэши, но это упрощает жизнь для создания реальных объектов и их использования. Вот наш класс User:
class User attr_reader :neo_id attr_accessor :uid, :name, :image_url, :location, :token def initialize(node) @neo_id = node["self"].split('/').last.to_i @uid = node["data"]["uid"] @name = node["data"]["name"] @image_url = node["data"]["img_url"] @location = node["data"]["location"] @token = node["data"]["token"] end ... end
Использование реальных объектов позволяет нам связать некоторые методы, чтобы помочь нам. Например, клиент Facebook этого пользователя, который использует токен, который мы сохранили при аутентификации, и самоцвет Koala, чтобы дать нам одобренное соединение с Facebook.
def client @client ||= Koala::Facebook::API.new(self.token) end
Давайте сделаем один шаг назад и посмотрим на линию раньше. Он использует гем Sidekiq для запуска фонового задания с именем ImportFacebookProfile.
module Job class ImportFacebookProfile include Sidekiq::Worker def perform(uid) user = User.find_by_uid(uid) ... # Import Friends friends = user.client.get_connections("me", "friends") friends.each do |friend| Sidekiq::Client.enqueue(Job::ImportFriends, uid, friend["id"]) Job::ImportMutualFriends.perform_at(120, uid, friend["id"]) end end end end
Этот работник заводит друзей на Facebook и создает два набора рабочих мест. ImportFriends, который сразу же добавляется в очередь, который выполняет фактический импорт друга, и ImportMutualFriends, который добавляется в очередь через 2 минуты.
module Job class ImportFriends include Sidekiq::Worker def perform(uid, person_id) user = User.find_by_uid(uid) person = user.client.get_object(person_id) friend = User.create_from_facebook(person) # Make them friends commands = [] commands << [:create_unique_relationship, "friends_index", "ids", "#{uid}-#{person_id}", "friends", user.neo_id, friend.neo_id] commands << [:create_unique_relationship, "friends_index", "ids", "#{person_id}-#{uid}", "friends", friend.neo_id, user.neo_id] batch_result = $neo_server.batch *commands ...
Задание ImportFriends извлекает полный профиль друга из Facebook и создает два «дружеских» отношения с пользователем (каждый идет в одну сторону). Задание ImportMutualFriends (полностью показано ниже) связывает друга с другими друзьями с помощью команды API Graph Graph для друга:
module Job class ImportMutualFriends include Sidekiq::Worker def perform(uid, person_id) user = User.find_by_uid(uid) person = user.client.get_object(person_id) friend = User.create_from_facebook(person) # Import mutual friends mutual_friends = user.client.get_connections("me", "mutualfriends/#{person_id}") commands = [] # Make them friends mutual_friends.each do |mutual_friend| uid = mutual_friend["id"] node = User.find_by_uid(uid) unless node person = user.client.get_object(uid) node = User.create_from_facebook(person) end commands << [:create_unique_relationship, "friends_index", "ids", "#{uid}-#{person_id}", "friends", node.neo_id, friend.neo_id] commands << [:create_unique_relationship, "friends_index", "ids", "#{person_id}-#{uid}", "friends", friend.neo_id, node.neo_id] end batch_result = $neo_server.batch *commands end end end
С этими друзьями и друзьями друзей мы можем использовать Cypher в нашей модели User, чтобы собрать friend_matrix. Обратите внимание на использование параметризованных запросов шифров. Заманчиво просто вставить его в строку, но неэффективно, так как Neo4j придется анализировать его каждый раз. С параметрами Neo4j просто анализирует его один раз, и в следующий раз он готов к выполнению.
def friend_matrix cypher = "START me = node({id}) MATCH me -[:friends]-> friends -[:friends]-> fof WHERE fof <> me RETURN friends.name, collect(fof.name) ORDER BY COUNT(fof) " $neo_server.execute_query(cypher, {:id => @neo_id})["data"] end
У некоторых людей есть сотни друзей на Facebook, и наша визуализация выглядит не очень хорошо, когда мы пересекаем 50 друзей. Поэтому вместо того, чтобы визуализировать все связи, мы выберем случайную выборку из 20-50 друзей. Мы симулируем друзей, которые случайно появились на вашем дне рождения. Мы создадим объект JSON, который мы передадим в D3.js для визуализации для нас.
get '/visualization' do @user = current_user random_number = 20 + Random.rand(31) @user.friend_matrix.sample(random_number).map{|fm| {"name" => fm[0], "follows" => fm[1]} }.to_json end
Мы повторно используем визуализацию D3 Chord, которую мы видели ранее, и это все, что нужно.
Вы можете попробовать NeoSocial для себя на http://neosocial.herokuapp.com . Как всегда, полный пример приложения доступен на github .