Создание отношений в модели Neo4J с помощью after_save

Поэтому я прошу прощения за то, как нубистские эти вопросы могут показаться. Я новичок в рельсах и в качестве первой задачи я также привел Neo4J, так как он выглядел как нельзя лучше, если я буду развивать проект.

Я объясню последовательность действий и покажу пример кода. Я пытаюсь добавить в шаге 3-5 сейчас.

  1. Пользователь входит через FB
  2. Первый логин создает пользовательский узел. Если пользователь существует, он просто получает этот пользователь + узел
  3. После того, как пользовательский узел создан, камень коала используется для доступа к FB Graph API
  4. Получает список друзей каждого друга с помощью приложения.
  5. Пройдите через каждого друга и добавьте двусторонние дружеские отношения между двумя пользователями

Поскольку 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

Вашим моделям очень легко стать такими грязными сумками. Многие блоги будут поощрять это. Боритесь с желанием!

Это должно быть хорошим началом. Дайте мне знать, если у вас есть какие-либо вопросы или если я что-то напортачил, кода много, и, возможно, мне понадобится уточнить или настроить его. Надеюсь, это поможет.

Другие вопросы по тегам