ソーシャルグラフの実装に関する考察

こんにちは



私たちは皆、ソーシャルネットワークの使用に慣れています。 その基盤の1つは、ユーザー間の社会的に重要な関係の確立です。 通常、これらの接続は友情またはファン(フォロワー)です。



私が何を思いついたのかはわかりませんが、学校を卒業した後(教師として働いています)、学校のウェブサイトでソーシャルグラフの機能を実現するのに役立つと思う私のお気に入りのレールに何かを作成することにしました。 そして、2種類の接続に限定しないことにしました。



ソーシャルグラフを空想して、Railsコードを記述してみましょう。







しばらく前に、RORプロジェクトでのソーシャルコネクションの機能の実装を数回取り扱わなければなりませんでした。 最初のケースでは、参加者間の友情が実現したプロジェクトでした。2番目のケースでは、「フォロワー」のつながりが作成されました。 両方のプロジェクトは商用です-私はそれらに名前を付けません。 ごめんなさい



一般的なポイントは、 フレンドシップに似た名前で接続が作成され、2つのユーザー識別子とこの接続のステータスがあったことです。 保留中 -友情の申請が提出され、確認待ちです。 承認 -申請が確認されてアクティブであり、 拒否されました -申請が拒否され、 削除されました -接続が削除されました。



さらに、私は通常、1人の人物1から2人の人物への接続を作成すると(私が見た実装では) 、2番目のツイン接続が作成され 、ユーザーIDが再配置される点のみが異なることに気付きました。 ツインレコードのステータスは、変更のたびにオリジナルからコピーされます。 このアプローチは理解できる-特定のユーザーのリンクの選択は、データベースへの単一のクエリによって実行されます。 ただし、データベースに追加のレコードが必要であり、レコードのステータスの変更を制御する必要があります。



今後は、コードのバージョンでレコードを作成しないことを決定し、データベースに2つのクエリを送信するという道筋をたどります。



世界はソーシャルネットワークに表示されるよりも複雑です





大規模なプロジェクトでは、多数のリンクが提供されません。 なんで? 知りません おそらく、人間の精神はまだ準備ができていないかもしれませんが...地元の大学の元教師の一人とのプライベートな会話では、考えがすり抜けました: 人と人とのコミュニケーションはネットワークで提示されるよりも複雑です 。 教師と生徒、上司と部下、役員とその兵士、サイト管理者、ユーザー、親と子など、などがあります。



気づいた? 多くの場合、関係における人々の役割は同等ではなく、あなたにとって簡単ではありません-友人。 すべてがもう少し複雑です。



また、原則として、ソーシャルコネクションにはコンテキスト(生活、仕事、軍隊、学校)があります。 この接続が確立された場所。



現在、ソーシャルネットワークで嫌いなものは何ですか? ランダムな知人や自分だけが知っている人を追加し、監査中に「実績」から削除する場合( 機嫌が悪い場合)、時には説明する必要があります-申し訳ありませんが、 あなたは私にとって敵ではありません。個人的に私はあなたに対して何も反対していません -しかし、 私はもうあなたを「友人」のリストに入れたくありません-私たちは数年間お互いを見ていません( そしてあなたの名前さえ覚えていません )-申し訳ありませんが、あまり意味がありません。



これは私が社会にあれば素晴らしいことになるだろう。 さまざまなオプションがネットワークで提供されました-友人、スポーツコーチ、私の祖母、学校のクラスメート、 飲酒仲間 、職場の同僚、上司、部門長など



Rails 3を45分間使った後、先生の心を突然刺激する何かのプロトタイプを投げようとしました。



モデル





モデル(これをグラフと呼びます)には、2つのユーザーID(アプリケーションの申請者と受信者)、アプリケーションのステータス、送信者の役割と受信者の役割、およびソーシャルコミュニケーションのコンテキストが含まれます。

rails g model graph context:string sender_id:integer sender_role:string recipient_id:integer recipient_role:string state:string
      
      







これにより、次の移行が可能になります。

 class CreateGraphs < ActiveRecord::Migration def self.up create_table :graphs do |t| t.string :context t.integer :sender_id t.string :sender_role t.integer :recipient_id t.string :recipient_role t.string :state t.timestamps end end def self.down drop_table :graphs end end
      
      







コンソールで実行します。

 rake db:migrate
      
      





これにより、データベース内の指定されたフィールドを持つ必要なテーブルが作成されます。



グラフモデルファイル自体で、 ステートマシンを使用して、グラフ要素が受け入れることができる状態決定し、 スコープにより、データベースクエリに必要な条件を追加できます。



 class Graph < ActiveRecord::Base scope :pending, where(:state => :pending) scope :accepted, where(:state => :accepted) scope :rejected, where(:state => :rejected) scope :deleted, where(:state => :deleted) #state pending, accepted, rejected, deleted state_machine :state, :initial => :pending do event :accept do transition :pending => :accepted end event :reject do transition :pending => :rejected end event :delete do transition all => :deleted end event :initial do transition all => :pending end end end
      
      







Userモデル(すべてのRailsアプリにあります)に、最初にメソッドgraph_toを追加します。このメソッドは 、このユーザーにグラフ要素を返す(グラフ要素が存在する場合)か、単に新しい要素を作成します。



あるコンテキストで、現在のユーザーから別のユーザーに、自分が誰かであり、受信者も誰か(事前定義されたロールに従って)のグラフ要素を作成します。

デフォルトでは、コンテキストは生命であり、ユーザーには役割(友人)があります。



 class User < ActiveRecord::Base def graph_to(another_user, opts={:context=>:live, :me_as=>:friend, :him_as=>:friend}) Graph.where(:context=>opts[:context], :sender_id=>self.id, :sender_role=>opts[:me_as], :recipient_id=>another_user, :recipient_role=>[:him_as]).first || graphs.new( :context=>opts[:context], :sender_role=>opts[:me_as], :recipient_id=>another_user.id, :recipient_role=>opts[:him_as]) end end
      
      







実験には、ユーザー関係の多くの記録が必要です。 したがって、私はレーキを作成しました。これにより、コンソールから数十人のユーザーを作成し、それらの間にランダム接続を確立できます。



ルーブルで読むことができない人のために説明します。



 namespace :db do namespace :graphs do # rake db:graphs:create desc 'create graphs for development' task :create => :environment do i = 1 puts 'Test users creating' 100.times do |i| u = User.new( :login => "user#{i}", :email => "test-user#{i}@ya.ru", :name=>"User Number #{i}", :password=>'qwerty', :password_confirmation=>'qwerty' ) u.save puts "test user #{i} created" i = i.next end#n.times puts 'Test users created' contexts = [:live, :web, :school, :job, :military, :family] roles={ :live=>[:friend,:friend], :web=>[:moderator, :user], :school=>[:teacher, :student], :job=>[:chief, :worker], :military=>[:officer, :soldier], :family=>[:child, :parent] } users = User.where("id > 10 and id < 80") #70 users test_count = 4000 test_count.times do |i| sender = users[rand(69)] recipient = users[rand(69)] context = contexts.rand # :job role = roles[context].shuffle # [:worker, :chif] # trace p "test graph #{i}/#{test_count} " + sender.class.to_s+" to "+recipient.class.to_s + " with context: " + context.to_s graph = sender.graph_to(recipient, :context=>context, :me_as=>role.first, :him_as=>role.last) graph.save # set graph state reaction = [:accept, :reject, :delete, :initial].rand graph.send(reaction) end# n.times end# db:graphs:create end#:graphs end#:db
      
      







反転グラフ要素



先ほど、双子の社会記録を作成したくないと言ったので、前方と反対の両方の方向でそれぞれのつながりを知覚する必要があります。



これを行うには、ユーザーモデルに行を追加します。

 has_many :graphs, :foreign_key=>:sender_id has_many :inverted_graphs, :class_name => 'Graph', :foreign_key=>:recipient_id
      
      





各ユーザーには多くの直接リンク(リンクの開始者)があり、逆にソーシャルリンクの要求の受信者には逆リンクがあります。 これらの要素は、異なる外部キーのみが異なります。



このユーザーのすべてのソーシャル接続を選択するには、彼の直接接続とフィードバック接続のすべてを選択し、レコードの配列を結合する必要があります。 たとえば、私の仕事から追加されたすべてのボスを選択するには、次のように記述します。

 def accepted_chiefs_from_job chiefs = graphs.accepted.where(:context => :job, :recipient_role=>:chief) # my graphs _chiefs = inverted_graphs.accepted.where(:context => :job, :sender_role=>:chief) # foreign graphs chiefs | _chiefs end
      
      





オペレーター| 配列を結合する演算子です。 私にとってはとても美しいです



少しのメタプログラミングとルビーの魔法



関係には多くのコンテキストとユーザーロールがあります。 上記のaccepted_chiefs_from_jobメソッドに似た多くのメソッドが必要です。これは、追加することに同意したジョブからすべての上司を選択します。 それらを手動で書くとは思わない?

Ruby自身が必要なメソッドを作成し、適切な選択を行うように、メタプログラミングを使用します。 これには魔法のメソッドmethod_missing(method_name、* args)が役立ちます。 このメソッドは、Rubyがメソッドを見つけられないときに呼び出されます。 ここで、グラフからデータを選択する試みに出会った場合に何をする必要があるかを説明します。



Rubyは次のようなメソッドを作成します。

 user.accepted_friends_from_live user.rejected_friends_from_live user.deleted_friends_from_live user.deleted_chiefs_from_job user.accepted_chiefs_from_job user.rejected_chiefs_from_job user.accepted_teachers_from_school user.deleted_teachers_from_school
      
      







以下をユーザーモデルに追加します。

 def method_missing(method_name, *args) if /^(.*)_(.*)_from_(.*)$/.match(method_name.to_s) match = $~ state = match[1].to_sym role = match[2].singularize.to_sym context = match[3].to_sym graphs.send(state).where(:context => context, :recipient_role=>role) | inverted_graphs.send(state).where(:context => context, :sender_role=>role) else super end end
      
      







method_missing(method_name、* args)がメソッドを見つけられない場合、定期的に解析を試みます。 規則性がグラフのメソッドの名前に適合する場合、カット自体はラインから受信したデータに基づいてリクエストをコンパイルし、結果を返します。 呼び出されたメソッドが通常のスケジュールに適合しない場合、 method_missing(method_name、* args)は単に標準の動作superに移行し、おそらくコード実行エラーが発生します。



ユーザー概要コード:

 class User < ActiveRecord::Base has_many :pages has_many :graphs, :foreign_key=>:sender_id has_many :inverted_graphs, :class_name => 'Graph', :foreign_key=>:recipient_id def method_missing(method_name, *args) if /^(.*)_(.*)_from_(.*)$/.match(method_name.to_s) match = $~ state = match[1].to_sym role = match[2].singularize.to_sym context = match[3].to_sym graphs.send(state).where(:context => context, :recipient_role=>role) | inverted_graphs.send(state).where(:context => context, :sender_role=>role) else super end end def graph_to(another_user, opts={:context=>:live, :me_as=>:friend, :him_as=>:friend}) Graph.where(:context=>opts[:context], :sender_id=>self.id, :sender_role=>opts[:me_as], :recipient_id=>another_user, :recipient_role=>[:him_as]).first || graphs.new( :context=>opts[:context], :sender_role=>opts[:me_as], :recipient_id=>another_user.id, :recipient_role=>opts[:him_as]) end end
      
      





まあそれはすべてです



次にレーキを行います:

 rake db:graphs:create
      
      





Railsコンソールを実行する

 rails c
      
      





私たちは実行しようとします:

 u = User.find(10) u.graph_to(User.first, :context=>:job, :me_as=>:boss, :him_as=>:staff_member) u.graph_to(User.last, :context=>:school, :me_as=>:student, :him_as=>:teacher) u.graph_to(User.find(20), :context=>:school, :me_as=>:student, :him_as=>:school) u.accepted_friends_from_live u.rejected_friends_from_live u.deleted_friends_from_live u.deleted_chiefs_from_job u.accepted_chiefs_from_job u.rejected_chiefs_from_job
      
      







PS:

応用プログラマーは学校の先生から尊敬と幸運を!



All Articles