Создание отношений в модели Neo4J с помощью after_save
Поэтому я прошу прощения за то, как нубистские эти вопросы могут показаться. Я новичок в рельсах и в качестве первой задачи я также привел Neo4J, так как он выглядел как нельзя лучше, если я буду развивать проект.
Я объясню последовательность действий и покажу пример кода. Я пытаюсь добавить в шаге 3-5 сейчас.
- Пользователь входит через FB
- Первый логин создает пользовательский узел. Если пользователь существует, он просто получает этот пользователь + узел
- После того, как пользовательский узел создан, камень коала используется для доступа к FB Graph API
- Получает список друзей каждого друга с помощью приложения.
- Пройдите через каждого друга и добавьте двусторонние дружеские отношения между двумя пользователями
Поскольку 3-5 требуется только при первом присоединении пользователя, я подумал, что смогу сделать это способом, связанным с after_save
Перезвоните. В этой логике есть недостаток, так как в какой-то момент мне потребуется обновить пользователя дополнительными атрибутами, и он снова вызовет after_save. Могу ли я предотвратить это при обновлении?
SessionsController для справки
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end
def destroy
session.delete(:user_id)
redirect_to root_path
end
Так что в моем user.rb у меня есть что-то вроде этого
has_many :both, :friendships
after_save :check_friends
def self.from_omniauth(auth)
@user = User.where(auth.slice(:provider, :uid)).first
unless @user
@user = User.new
# assign a bunch of attributes to @user
@user.save!
end
return @user
end
def facebook
@facebook ||= Koala::Facebook::API.new(oauth_token)
block_given? ? yield(@facebook) : @facebook
rescue Koala::Facebook::APIError => e
logger.info e.to_s
nil
end
def friends_count
facebook { |fb| fb.get_connection("me", "friends", summary: {}) }
end
def check_friends(friendships)
facebook.get_connection("me", "friends").each do |friend|
friend_id = friend["id"]
friend_node = User.where(friend_id)
Friendship.create_friendship(user,friend_node)
return true
end
end
friendship.rb
from_class User
to_class User
type 'friendship'
def self.create_friendship(user,friend_node)
friendship = Friendship.create(from_node: user, to_node: friend_node)
end
Я не уверен, что я на правильном пути с тем, как создать узел отношений. Как я только что создал @user
Как я могу включить это в мой check_friends
метод и получить узел пользователя и друга так правильно, чтобы я мог связать их вместе.
Сейчас он не знает, что user и friend_user являются узлами
Если вы видите другую плохую практику кода, пожалуйста, дайте мне знать!
Заранее: спасибо за помощь @subvertallchris. Я уверен, что вы будете отвечать на многие мои вопросы, как этот.
1 ответ
Это действительно отличный вопрос! Я думаю, что вы на правильном пути, но есть несколько вещей, которые вы можете изменить.
Во-первых, вам нужно настроить это has_many
метод. Ваши ассоциации всегда должны заканчиваться на узле, а не на классах ActiveRel, поэтому вам нужно переписать это примерно так:
has_many :both, :friends, model_class: 'User', rel_class: 'Friendship'
В противном случае вы столкнетесь с некоторыми проблемами.
Вы можете рассмотреть возможность переименования своего типа отношений в интересах стилистической последовательности Neo4j. У меня есть много плохих примеров, так что извините, если дал вам плохие идеи. FRIENDS_WITH
было бы лучше имя отношения.
Что касается решения вашей большой проблемы, вы можете многое сделать здесь.
РЕДАКТИРОВАТЬ! Дерьмо, я забыл самую важную часть! Дитч, что after_save
Обратный вызов и сделать загрузку существующих / создать новое поведение пользователя двумя методами.
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
@user = user.nil? ? User.create_from_omniauth(env["omniauth.auth"]) : user
session[:user_id] = @user.id
redirect_to root_url
end
def destroy
session.delete(:user_id)
redirect_to root_path
end
end
class User
include Neo4j::ActiveNode
# lots of other properties
has_many :both, :friends, model_class: 'User', rel_class: 'Friendship'
def self.from_omniauth(auth)
User.where(auth.slice(:provider, :uid)).limit(1).first
end
def self.create_from_omniauth(auth)
user = User.new
# assign a bunch of attributes to user
if user.save!
user.check_friends
else
# raise an error -- your user was neither found nor created
end
user
end
# more stuff
end
Это решит вашу проблему с началом. Вы можете захотеть обернуть все это в транзакцию, так что читайте об этом в вики.
Но мы еще не закончили. Давайте посмотрим на ваш оригинал check_friends
:
def check_friends(friendships)
facebook.get_connection("me", "friends").each do |friend|
friend_id = friend["id"]
friend_node = User.where(friend_id)
Friendship.create_friendship(user,friend_node)
return true
end
end
Вы на самом деле не передаете это аргумент, так что избавьтесь от этого. Кроме того, если вы знаете, что ищете только один узел, используйте find_by
, Я собираюсь предположить, что есть facebook_id
собственность на каждого пользователя.
def check_friends
facebook.get_connection("me", "friends").each do |friend|
friend_node = User.find_by(facebook_id: friend["id"])
Friendship.create_friendship(user,friend_node) unless friend_node.blank?
end
end
create_friendship
Метод должен возвращать значение true или false, поэтому просто сделайте так, чтобы последний оператор метода сделал это, и вы можете вернуть все, что он вернет. Это так просто, как это:
def self.create_friendship(user, friend_node)
Friendship.new(from_node: user, to_node: friend_node).save
end
create
не возвращает true или false, он возвращает результирующий объект, поэтому цепочка save
чтобы ваш новый объект получит то, что вы хотите. Вам не нужно устанавливать переменную там, если вы не планируете использовать ее больше в методе.
На этом этапе вы можете легко добавить after_create
обратный вызов вашей модели ActiveRel, которая будет делать что-то на from_node
, который всегда является пользователем, которого вы только что создали. Вы можете обновить свойства пользователя так, как вам нужно. Контроль такого рода поведения - вот почему ActiveRel существует.
Я бы, наверное, еще немного переделал. Начните с перемещения вашего facebook
вещи в модуль. Это сделает вашу модель пользователя более чистой и более сфокусированной.
# models/concerns/facebook.rb
module Facebook
extend ActiveSupport::Concern
def facebook
@facebook ||= Koala::Facebook::API.new(oauth_token)
block_given? ? yield(@facebook) : @facebook
rescue Koala::Facebook::APIError => e
logger.info e.to_s
nil
end
def friends_count
facebook { |fb| fb.get_connection("me", "friends", summary: {}) }
end
end
# now back in User...
class User
include Neo4j::ActiveNode
include Facebook
# more code...
end
Вашим моделям очень легко стать такими грязными сумками. Многие блоги будут поощрять это. Боритесь с желанием!
Это должно быть хорошим началом. Дайте мне знать, если у вас есть какие-либо вопросы или если я что-то напортачил, кода много, и, возможно, мне понадобится уточнить или настроить его. Надеюсь, это поможет.