4

所以我为这些问题看起来多么愚蠢而道歉。我是 Rails 的新手,作为第一项任务,我还引入了 Neo4J,因为如果我发展这个项目,它似乎是最合适的。

我将解释操作流程,然后展示一些示例代码。我现在正在尝试添加第 3-5 步。

  1. 用户通过FB登录
  2. 第一次登录会创建一个用户节点。如果用户存在,它只是检索该用户+节点
  3. 用户节点创建后,通过koala gem 访问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

友谊.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 的帮助。我相信你会像这样回答我的很多问题。

4

1 回答 1

5

这是一个非常好的问题!我认为你走在正确的轨道上,但你可以改变一些事情。

首先,您需要调整该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

这将解决您启动它的问题。您可能希望将整个事物包装在事务中,因此请在 wiki 中阅读相关内容。

但我们还没有完成。让我们看看你的原件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

你的模特很容易变成这些凌乱的抓包。很多博客都会鼓励这一点。对抗冲动!

这应该是一个好的开始。如果您有任何问题或我搞砸了任何事情,请告诉我,有很多代码,我可能需要澄清或调整其中的一些。不过,希望它有所帮助。

于 2014-10-24T23:07:54.960 回答