SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Ruby on Rails + Curl(AD)

Curl+JRuby+Google App EngineでTwitter風アプリを作る
~ローカル環境構築編~

第4回

  • このエントリーをはてなブックマークに追加

Twitter風サービスの構築2

友人モデル

 friendshipsテーブルはusersテーブルに所属しているので、 所属をbelongs_toで宣言しています。また、friend_idカラム(Friendshipクラスのfriend属性)も usersテーブルに所属しているのでbelongs_toで宣言しています、ただし属性名、カラム名が規約通りではないので :class_name、foreign_keyで設定しています。

 user属性の方は friendshipsテーブルの user_id カラムに対応し、所属先は Userクラス(usersテーブル)という規約通りなのでclass_name、foreign_keyを省略しています。

 以下はいずれも小さなメソッドばかりですが、コントローラーにはロジックを含む処理は書かずに、モデルに集めるようにするとコードの重複が減り、テストもしやすいメンテナンス性の高いコードになります。

  • friendsクラスメソッドは、指定されたmy_idの友人の一覧を戻します。ただし、友人には自分自身も含まれるので、自分自身は含まないようにしています。
  • not_friend_users クラスメソッドは、指定されたmy_idの友人でないユーザーの一覧を戻します。
  • create_selfクラスメソッドは、指定されたmy_idの自分自身を友人として登録します。

 not_friend_userssで使われている find_all_by_user_id(id)メソッドですが、これは find(:all, :conditions=>["user_id = ?", id]) と同等で、 "find_all_by_カラム名" のメソッドが実行されるとActiveRecordが動的にメソッドを生成します。

class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
  
  def self.friends(my_id)
    Friendship.all(:conditions => ["user_id = ? and friend_id <> ?", my_id, my_id])
  end

  def self.not_friend_users(my_id)
    User.all(:conditions => ["id not in (?)", Friendship.find_all_by_user_id(my_id).map(&:friend_id)])
  end
  
  def self.create_self(my_id)
    Friendship.new(:user_id => my_id, :friend_id => my_id).save!
  end
end

友人コントローラー

 友人コントローラーは、友人の一覧、追加、削除を行うだけの簡単なコントローラーです。

class FriendshipsController < ApplicationController

  def index
    @friendships = Friendship.friends(@user_id)
  end

  def new
    @friendship = Friendship.new
    @not_friend_users = Friendship.not_friend_users(@user_id)
  end

  def create
    @friendship = Friendship.new(params[:friendship])
    @friendship.user_id = @user_id

    if @friendship.save
      flash[:notice] = 'フォローに登録しました'
      redirect_to(friendships_url)
    else
      render :action => "new"
    end
  end

  def destroy
    @friendship = Friendship.find(params[:id])
    @friendship.destroy

    redirect_to(friendships_url)
  end
end

つぶやきモデル

 statusesテーブルはusersテーブルに所属しているので、belongs_toで宣言しています。

 all_frendsクラスメソッドは、指定されたmy_idユーザーの友人のつぶやきの一覧を取得しています。

 api_dataメソッドはCurl等のアクセスに対し、戻すデータを組み立てるメソッドです。データは statusesから所得したレコードをHashに変換したものです。ただし user_idの代わりにユーザー情報を含むHash、{id=>ユーザーid、 screen_name=>ユーザー名、profile_image_url=>アイコンのURL}を"user"をキーとして追加しています。

 また、画像のURLはモデルが関知すべき情報ではないので、image_url引数で画像URLを求める手続きオブジェクト(関数)を渡してもらい、それを image_url.call で呼び出しています。

class Status < ActiveRecord::Base
  belongs_to :user
  
  def self.all_frends(my_id)
    Status.all(:conditions => ["friendships.user_id = ?", my_id],
                    :joins => "JOIN friendships ON statuses.user_id = friendships.friend_id",
                    :order => "created_at DESC")
  end
  
  def api_data(image_url)
    status_attrs = self.attributes
    user_attrs = User.find(self.user_id, :select => "id,screen_name").attributes
    user_attrs["profile_image_url"] = image_url.call(self.user.profile_image_path)
    status_attrs["user"] = user_attrs
    status_attrs
  end
end

つぶやきコントローラー

 つぶやきコントローラーは、友人のつぶやき一覧と、つぶやきの新規作成が主な処理です。ただし連載2回目同様にCurlのコードをクライアントに送るstart処理もあります。この処理自体の認証は外してあります。

 つぶやき一覧(friends_timeline)処理では、つぶやき情報を取得し、HTMLやJSON形式でデータを戻しています。ただし、JSONデータに含まれるアイコン画像のURLを戻す手続きオブジェクト(関数)をto_image_url変数に代入し、api_dataメソッドに渡しています。 index は friends_timeline にリダイレクトするようにしました。

 つぶやきの新規作成(create)処理は Scaffoldの作ったコードのXMLをJSONに、user_idの設定を変更しただけです。

class StatusesController < ApplicationController

  skip_before_filter :authenticate, :only => :start
  
  def index
    redirect_to friends_timeline_statuses_url
  end
  
  def start
    render :layout => false, :content_type => 'text/vnd.curl'
  end
    
  def friends_timeline
    @statuses = Status.all_frends(@user_id)
    @status = Status.new
    to_image_url = lambda{|path| url_for.sub(/ #{friends_timeline_statuses_path}$/, '/images/') + path}
    
    respond_to do |format|
      format.html # index.html.erb
      format.json  { render :json => @statuses.map{|e| e.api_data(to_image_url)}.to_json }
    end
  end

  def create
    @status = Status.new(params[:status])
    @status.user_id = @user_id

    respond_to do |format|
      if @status.save
        format.html { redirect_to friends_timeline_statuses_url }
        format.json  { render :json => @status, :status => :created, :location => @status }
      else
        format.html { render :action => "new" }
        format.json  { render :json => @status.errors, :status => :unprocessable_entity }
      end
    end
  end
end

Curlクライアントの変更

 このTwitter風サービスにCurlクライアントからアクセスするにはサーバのURLを次のように変更します。

{let server_url = "http://localhost:3000/"}

まとめ

 今回は、Curlを使ったTwitterクライアントとRuby on Railsを使った Twitter風サービスを作ってみました。 Ruby on Railsのサーバ側は複数テーブルの関連や認証などを含み、本格的なシステムの作る際の参考になると思います。また、CurlもRuby on RailsもJSONデータなどを簡単に扱えるのでTwitterのような Web+APIのシステムが手軽に作れる事を感じていただけたかと思います。

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
Ruby on Rails + Curl連載記事一覧

もっと読む

この記事の著者

吉田裕美(ヨシダユウミ)

有限会社 EY-Office 取締役CADのベンチャー企業でCADのコア部分や図面管理システムなどの開発に従事した後、独立しJava,Ruby,PerlでWebアプリを中心に開発してきた。現在は殆どの開発はRuby on Rails。ここ数年はソフトウェアエンジニアの教育に興味をもち、従来の知識偏重な教育ではなく現実の問題を解決できるエンジニアを育てる教育に注力している。またLisp等に関心...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4307 2009/09/24 15:08

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング