review askeet Day 4

詳細画面をtableからdiv化する。

このテーブルでできたサイトを直す。

場所: askeet2/apps/frontend/modules/question/templates/showSuccess.php

<?php use_helper('Date') ?>
 
<div class="interested_block">
  <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
    <?php echo count($question->getInterests()) ?>
  </div>
</div>
 
<h2><?php echo $question->getTitle() ?></h2>
 
<div class="question_body">
  <?php echo $question->getBody() ?>
</div>
 
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>

  <div class="answer">

    posted by <?php echo $answer->getUser()->getFirstName().' '.$answer->getUser()->getLastName() ?> 
    on <?php echo format_date($answer->getCreatedAt(), 'p') ?>

    <div>
      <?php echo $answer->getBody() ?>
    </div>

  </div>

<?php endforeach; ?>
</div>


うん、変わった。

テストデータの追加

答えと妥当さのデータをテストデータに入れる。
場所: askeet2/data/fixtures/test_data.yml

User:
  anonymous:
    nickname:   anonymous
    first_name: Anonymous
    last_name:  Coward

  crazyup:
    nickname:   crazyup
    first_name: Shota
    last_name:  Enomoto

  dinotaro:
    nickname:   dino
    first_name: Taro
    last_name:  Dino

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: crazyup
    body:  |
      We shall meet in front of the Dunkin'Donuts before dinner, 
      and I haven't the slightest idea of what I can do with her. 
      She's not interested in programming, space opera movies nor insects.
      She's kinda cute, so I really need to find something 
      that will keep her to my side for another evening.

  q2:
    title: What can I offer to my step mother?
    user_id: anonymous
    body:  |
      My stepmother has everything a stepmother is usually offered
      (watch, vacuum cleaner, earrings, del.icio.us account). 
      Her birthday comes next week, I am broke, and I know that 
      if I don't offer her something sweet, my girlfriend 
      won't look at me in the eyes for another month.

  q3:
    title: How can I generate traffic to my blog?
    user_id: dinotaro
    body:  |
      I have a very swell blog that talks 
      about my class and mates and pets and favorite movies.

Interest:
  i1: { user_id: crazyup, question_id: q1 }
  i2: { user_id: dinotaro, question_id: q1 }
  i3: { user_id: dinotaro, question_id: q2 }
  i4: { user_id: crazyup, question_id: q2 }

Answer:
  a1_q1:
    question_id: q1
    user_id:     dinotaro
    body:        |
      You can try to read her poetry. Chicks love that kind of things.

  a2_q1:
    question_id: q1
    user_id:     crazyup
    body:        |
      Don't bring her to a donuts shop. Ever. Girls don't like to be
      seen eating with their fingers - although it's nice. 

  a3_q2:
    question_id: q2
    user_id:     crazyup
    body:        |
      The answer is in the question: buy her a step, so she can 
      get some exercise and be grateful for the weight she will
      lose.

  a4_q3:
    question_id: q3
    user_id:     crazyup
    body:        |
      Build it with symfony - and people will love it.

で、

php batch/load_data.php

確認する。

データが表示されている。

モデルの変更

まずはいちいちgetUserの中のgetFirstNameとか呼び出さなくても
getUserすれば苗字+名前が出るようにする。
現在、askeet2/lib/model/User.phpは空っぽなので
そこに__toStringで書く。

  public function __toString()
  {
    return $this->getFirstName().' '.$this->getLastName();
  }

あとはテンプレートを直すだけ。
さっそく、askeet2/apps/frontend/modules/question/templates/showSuccess.phpを直す。

    posted by <?php echo $answer->getUser() ?> 

同じことは繰り返さない

listSuccess.phpとshowSuccess.phpには同じコードがある。
同じことは繰り返さないということに反するのでフラグメントにする。
場所: askeet2/apps/frontend/modules/question/templates/_interested_user.php

  <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
    <?php echo count($question->getInterests()) ?>
  </div>

↑が書かれた部分を↓のコードに置き換える。
場所: askeet2/apps/frontend/modules/question/templates/listSuccess.php
場所: askeet2/apps/frontend/modules/question/templates/showSuccess.php

   <?php include_partial('interested_user',array('question' => $question)) ?>

にすると共通化ができる。これで同じところを何度も直す必要がなくなる。

オブジェクトモデルに項目を追加

場所: askeet2/config/schema.yml

  ask_question:
    _attributes:      { phpName: Question, idMethod: native }
    id:               { type: integer, required: true, primaryKey: true, autoIncrement: true }
    user_id:          { type: integer, foreignTable: ask_user, foreignReference: id }
    title:            { type: longvarchar }
    body:             { type: longvarchar }
    interested_users: { type: integer, default: 0 }
    created_at:       ~
    updated_at:       ~
モデルの再設定
symfony propel-build-model

モデルを再構築したらSQLを作ってMySQLに流す。
その後にテストデータを再びMySQLに流す。

symfony propel-build-sql
symfony propel-insert-sql
php batch/load_data.php

関心の更新をできるようにする

InnoDBなのでトランザクションを使えるように書きます。
場所: askeet2/lib/model/Interest.php

<?php

/**
 * Subclass for representing a row from the 'ask_interest' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Interest extends BaseInterest
{

  public function save($con = null)
  {
    $con= Propel::getConnection();
    try
      {
        $con->begin();

        $ret = parent::save($con);

        // interested_usersを更新
        $question = $this->getQuestion();
        $interested_users = $question->getInterestedUsers();
        $question->setInterestedUsers($interested_users + 1);
        $question->save($con);

        $con->commit();

        return $ret;
      }
    catch (Exception $e)
      {
        throw $e;
      }
  }

}
interested_userパーシャルの更新

場所: askeet2/apps/frontend/modules/question/templates/_interested_user.php

  <div class="interested_mark" id="mark_<?php echo $question->getId() ?>">
    <?php echo $question->getInterestedUsers() ?>
  </div>

修正したら、テストデータを更新する。

php batch/load_data.php

回答の投票システム

まずはschema.ymlに追加。
場所: askeet2/config/schema.yml

  ask_answer:
    _attributes:      { phpName: Answer, idMethod: native }
    id:               { type: integer, required: true, primaryKey: true, autoIncrement: true }
    question_id:      { type: integer, foreignTable: ask_question, foreignReference: id }
    user_id:          { type: integer, foreignTable: ask_user, foreignReference: id }
    body:             { type: longvarchar }
    relevancy_up:     { type: integer, default: 0 }
    relevancy_down:   { type: integer, default: 0 }
    created_at:       ~
できたら、モデルを再設定してDB更新
symfony propel-build-model
symfony propel-build-sql
symfony propel-insert-sql
Revancyクラスのsaveを書き換える

場所: askeet2/lib/model/Relevancy.php

<?php

/**
 * Subclass for representing a row from the 'ask_relevancy' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Relevancy extends BaseRelevancy
{

  public function save($con = null)
  {
    $con = Propel::getConnection();
    try
      {
        $con->begin();

        $ret = parent::save();

        // relevancy(妥当性)を更新する
        $answer = $this->getAnswer();
        if($this->getScore() == 1)
          {
            $answer->setRelevancyUp($answer->getRelevancyUp() + 1);
          }
        else
          {
            $answer->setRelevancyDown($answer->getRelevancyDown() + 1);
          }
        $answer->save($con);

        $con->commit();

        return $ret;
      }
    catch(Exception $e)
      {
        $con->rollback();
        throw $e;
      }
  }
}
Answerクラスにメソッドを追加

場所: askeet2/lib/model/Answer.php

<?php

/**
 * Subclass for representing a row from the 'ask_answer' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Answer extends BaseAnswer
{

  public function getRelevancyUpPercent()
  {
    $total = $this->getRelevancyUp() + $this->getRelevancyDown();

    return $total ? sprintf('$.0f',$this->getRelevancyUp() * 100 / $total) : 0;

  }

  public function getRelevancyDownPercent()
  {
    $total = $this->getRelevancyUp() + $this->getRelevancyDown();

    return $total ? sprintf('%.0f',$this->getRelevancyDown() * 100 / $total) : 0;
  }

}
テンプレートの変更

場所: askeet2/apps/frontend/modules/question/templates/showSuccess.php

<?php use_helper('Date') ?>
 
<div class="interested_block">
   <?php include_partial('interested_user',array('question' => $question)) ?>
</div>
 
<h2><?php echo $question->getTitle() ?></h2>
 
<div class="question_body">
  <?php echo $question->getBody() ?>
</div>
 
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>

  <div class="answer">

    <?php echo $answer->getRelevancyUpPercent() ?>% UP 
    <?php echo $answer->getRelevancyDownPercent() ?>% DOWN

    posted by <?php echo $answer->getUser() ?> 
    on <?php echo format_date($answer->getCreatedAt(), 'p') ?>

    <div>
      <?php echo $answer->getBody() ?>
    </div>

  </div>

<?php endforeach; ?>
</div>
テストデータに追加する。

場所: askeet2/data/fixtures/test_data.yml

User:
  anonymous:
    nickname:   anonymous
    first_name: Anonymous
    last_name:  Coward

  crazyup:
    nickname:   crazyup
    first_name: Shota
    last_name:  Enomoto

  dinotaro:
    nickname:   dino
    first_name: Taro
    last_name:  Dino

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: crazyup
    body:  |
      We shall meet in front of the Dunkin'Donuts before dinner, 
      and I haven't the slightest idea of what I can do with her. 
      She's not interested in programming, space opera movies nor insects.
      She's kinda cute, so I really need to find something 
      that will keep her to my side for another evening.

  q2:
    title: What can I offer to my step mother?
    user_id: anonymous
    body:  |
      My stepmother has everything a stepmother is usually offered
      (watch, vacuum cleaner, earrings, del.icio.us account). 
      Her birthday comes next week, I am broke, and I know that 
      if I don't offer her something sweet, my girlfriend 
      won't look at me in the eyes for another month.

  q3:
    title: How can I generate traffic to my blog?
    user_id: dinotaro
    body:  |
      I have a very swell blog that talks 
      about my class and mates and pets and favorite movies.

Interest:
  i1: { user_id: crazyup, question_id: q1 }
  i2: { user_id: dinotaro, question_id: q1 }
  i3: { user_id: dinotaro, question_id: q2 }
  i4: { user_id: crazyup, question_id: q2 }

Answer:
  a1_q1:
    question_id: q1
    user_id:     dinotaro
    body:        |
      You can try to read her poetry. Chicks love that kind of things.

  a2_q1:
    question_id: q1
    user_id:     crazyup
    body:        |
      Don't bring her to a donuts shop. Ever. Girls don't like to be
      seen eating with their fingers - although it's nice. 

  a3_q2:
    question_id: q2
    user_id:     crazyup
    body:        |
      The answer is in the question: buy her a step, so she can 
      get some exercise and be grateful for the weight she will
      lose.

  a4_q3:
    question_id: q3
    user_id:     crazyup
    body:        |
      Build it with symfony - and people will love it.

Relevancy:
  rel1:
    answer_id: a1_q1
    user_id:   crazyup
    score:     1

  rel2:
    answer_id: a1_q1
    user_id:   dinotaro
    score:     -1

書いたらバッチを走らせる。

php batch/load_data.php

ルーティング

http://review.askeet.localhost/question/show/id/1

http://review.askeet.localhost/question/what-shall-i-do-tonight-with-my-girlfriend
↑のようにするためにはQuestionテーブルにwhat-shall-i-do-tonight-with-my-girlfriendを保存すればよい。

と、言うことでschema.ymlを書こう。

場所: askeet2/config/schema.yml

  ask_question:
    _attributes:      { phpName: Question, idMethod: native }
    id:               { type: integer, required: true, primaryKey: true, autoIncrement: true }
    user_id:          { type: integer, foreignTable: ask_user, foreignReference: id }
    title:            { type: longvarchar }
    body:             { type: longvarchar }
    interested_users: { type: integer, default: 0 }
    stripped_title:   varchar(255)
    _uniques:
      unique_stripped_title: [stripped_title]
    created_at:       ~
    updated_at:       ~
モデルを書いたら再設定とデータベースを更新。
symfony propel-build-model
symfony propel-build-sql
symfony propel-insert-sql
タイトルから生成する。

場所: askeet2/lib/myTools.class.php

<?php

class myTools
{

  public static function stripText($text)
  {

    $text = strtolower($text);

    //単語以外をはぎ取る
    $text = preg_replace('/\W/',' ',$text);
    //空白文字をハイフンに置き換える
    $text = preg_replace('/\ +/','-',$text);
    //ハイフンをトリムする
    $text = preg_replace('/\-$/','',$text);
    $text = preg_replace('/^\-/','',$text);

    return $text;

  }

}
Questionクラスを書く

場所: askeet2/lib/model/Question.php

<?php

/**
 * Subclass for representing a row from the 'ask_question' table.
 *
 * 
 *
 * @package lib.model
 */ 
class Question extends BaseQuestion
{

  public function setTitle($v)
  {

    parent::setTitle($v);
    $this->setStrippedTitle(myTools::stripText($v));

  }

}
テストデータのリロード

キャッシュも捨てておきます。

symfony cc
php batch/load_data.php
テンプレートの書き換え

リンク部分を書き換える
場所: askeet2/apps/frontend/modules/question/templates/listSuccess.php

    <h2><?php echo link_to($question->getTitle(),'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>
Questionのshowアクションを直す。

場所: askeet2/apps/frontend/modules/actions/actions.class.php

  public function executeShow()
  {
    $c = new Criteria();
    $c->add(QuestionPeer::STRIPPED_TITLE,$this->getRequestParameter('stripped_title'));
    $this->question = QuestionPeer::doSelectOne($c);

    $this->forward404Unless($this->question);
  }


ただ、今の状態だと
http://review.askeet.localhost/question/show/stripped_title/what-shall-i-do-tonight-with-my-girlfriend
となる。
/show/stripped_title/は要らない。

だったら、ルーティングルールを変えればいい。

ということで変更。追加するところは一番最初。
場所: askeet2/apps/frontend/config/routing.yml

question:
  url:   /question/:stripped_title
  param: { module: question, action: show }


できた><