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

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

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

  1. Пользователь авторизуется через FB
  2. При первом входе создается пользовательский узел. Если пользователь существует, он просто извлекает этот пользователь + узел
  3. После создания пользовательского узла гем koala используется для доступа к 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

дружба.рб

  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 и получить узел пользователя и друга так правильно, чтобы я мог связать их вместе.

Прямо сейчас он не знает, что пользователь и друг_пользователь являются узлами

Если вы видите другую практику плохого кода, пожалуйста, дайте мне знать!

Заранее: спасибо за помощь @subvertallchris. Я уверен, что вы ответите на многие мои вопросы, подобные этому.


person Clam    schedule 23.10.2014    source источник
comment
Хахаха! Вы смотрите в будущее! Это большой вопрос. Дайте мне несколько минут, чтобы напечатать его.   -  person subvertallchris    schedule 25.10.2014
comment
Кроме того, не стесняйтесь, пишите мне по электронной почте, если вам когда-нибудь понадобится помощь и вы не хотите публиковать здесь по какой-либо причине. Я изучал Ruby, Rails и Neo4j одновременно, потому что это тоже подходило для моего проекта!   -  person subvertallchris    schedule 25.10.2014


Ответы (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

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

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

person subvertallchris    schedule 24.10.2014
comment
Хорошо, ничего не выдает ошибку, но отношения все равно не сохраняются. Я думаю, что friend_node может быть пустым. Я хочу посмотреть, правильно ли он находит узел friend_node = User.find_by(uid: friend["id"]) Каковы хорошие способы проверить, не пусто ли что-то в rails/ruby в моей модели, и распечатать это в моем представлении? - person Clam; 26.10.2014
comment
Я спрошу вас немного о Friend_type, может быть, в slack - person Clam; 26.10.2014