Visual Studio 2010 で CLI/C++ のインテリセンスが使える拡張アドイン Visual Assist X

以前、

C++/CLIにインテリセンスが必須な理由 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/793

なところで、Visual Studio 2010 では C++/CLI のインテリセンスだけが効かない。という話をしましたが、どうやら、有料の拡張アドインを入れれば C++/CLI のインテリセンスが使えます。

# そうそう、Visual Studio 2008 に戻せば、C++/CLI のインテリセンスが効くんですけどね。何故か、2010 を入れた後に 2008 を入れることができないんですよ。ここはなんとかしなくちゃなあと。やっぱり、2010 をアンインストールしてから、2008 -> 2010 の順に入れ直すしかないんですかね?

Visual Assist X – an add-in for Visual Studio by Whole Tomato Software
http://www.wholetomato.com/default.asp
Visual Assist X
http://visualstudiogallery.msdn.microsoft.com/D6678A1F-C700-4532-8C5E-A799D875063D/?SRC=Home

で、30日試用版がダウンロードできます。

ダウンロードしたら、*.vsix のファイルをダブルクリック。

するとインストーラーが起動します(実は、このあたり悩んだ…アドイン拡張アドインは違うのですね。

ちなみに、Assite X は、C++/CLI だけじゃなく、C# や Visual Basic などのインテリセンスも賢くしてくれます。色々機能があるので先のサイトでデモを見ると良いかと。

Visual Studio 2010 を起動して、「ツール」→「拡張機能マネージャー」を見ると、

のように Assist X が有効になっています。

ちなみに、オンライン ギャラリーを見ると、色々アドインがありますね(知らんかった)。

さて、肝心のインテリセンス機能ですが、こんな風に出ます。

やったッ!!! これで、開発効率が上がるぜッ!!! という訳で、パーソナルライセンスが $99。買うかどうか思案中ですね…思案しているのは、現在 C++/CLI の案件を抱えてないってことで。まぁ、サンプル作りとかにも使えるし、せっかく MVP Visual C++ を受賞したし、という訳なのですが。

カテゴリー: 開発, ツール | Visual Studio 2010 で CLI/C++ のインテリセンスが使える拡張アドイン Visual Assist X はコメントを受け付けていません

ASP.NET MVC から wordpress のデータを扱う(3)

実はEDM(Entity Data Model)では、あらかじめデータベースでリレーションを設定しておくと、モデルのほうでアソシエーションが設定されます…って、なんだか分かりませんね(汗)

要は、こんな感じです。
最初の SQL Server 2008 R2 上で、リレーションを設定します。

ダイアグラムではこんな風。

このあたり、.NET ラボの勉強会で話したのですが、SQL Server Management Studio はそれなりに新しくなっていくのに、このダイアグラムは SQL Server 7.0 の頃と変わり映えしません。時代遅れなのか、誰も使っていないのかというところですが、データベース上の外部キーって大切なんですけどねぇ。

これを Visual Studio の EDM を更新すると、こんな風にアソシエーションが設定されます。

ちなみに、データベース上では「リレーション」とか「外部キー」と言われるんですが、オブジェクト指向の方(CakePHPとかも)の O/R マッピングの世界では「アソシエーション」って言っているようです。どちらでもいいんですが、統一してほしいような気が(機能が違うからっていう意味もあるでしょうが)。

こういう風にしておくと、wp_posts モデルの中に wp_users というプロパティ(ナビゲーション)が出来きます。逆向きの wp_users モデルの中にもできますね。
これのナビゲーションを使うと、wp_posts.wp_users.display_name というように、関係するテーブルの列名を表示させることができるのです。

この機能をうまく使うと、ビューが簡単になります。

■ビューを作る

<%@ Page Title=&quot;&quot; Language=&quot;C#&quot; MasterPageFile=&quot;~/Views/Shared/Site.Master&quot; Inherits=&quot;System.Web.Mvc.ViewPage<IEnumerable<MvcWordpress.wp_posts>>&quot; %>

<asp:Content ID=&quot;Content1&quot; ContentPlaceHolderID=&quot;TitleContent&quot; runat=&quot;server&quot;>
	Recent
</asp:Content>

<asp:Content ID=&quot;Content2&quot; ContentPlaceHolderID=&quot;MainContent&quot; runat=&quot;server&quot;>

    <h2>最近投稿した記事</h2>
    <table>
        <tr>
            <th>ID</th>
            <th>post_date</th>
            <th>post_title</th>
            <th>post_author</th>
            <th>User.display_name</th>
            <th>guid</th>
        </tr>
        <% foreach (var item in Model) { %>
            <tr>
                <td><%: item.ID %></td> 
                <td><%: item.post_title %></td>
                <td><%: item.post_date %></td>
                <td><%: item.post_author %></td>
                <td><%: item.wp_users.display_name %></td>
                <td><%: item.guid %></td>
            </tr>
        <% } %>
    </table>
    <h2>最近投稿した記事(リンク版)</h2>
    <ul>
        <% foreach (var item in Model) { %>
        <li>
            <a href=&quot;<%: item.guid %>&quot;><%: item.post_title %></a>
            by <%: item.wp_users.display_name %>
            at <%: item.post_date %>
        </li>
        <% } %>
    </ul>
</asp:Content>

渡されるモデルは、wp_posts のコレクションということで、IEnumerable と指定し直します。こうすると、wp_posts の列名はそのまま使えるし、関連する wp_users のテーブルも「<%: item.wp_users.display_name %>」のように簡単に参照できます。

■コントローラーを直す

コントローラーのほうは、LINQ to Entities に直して、wp_posts テーブルだけを参照するようにします。参照先のテーブルは、自動的に読み込まれるので LINQ 自体も簡単になります。

public ActionResult Recent()
{
    int max = 5;
    wordpressEntities ent = new wordpressEntities();
    var model = (from p in ent.wp_posts
                 where p.post_status == "publish"
                 orderby p.post_date descending
                 select p).Take(max);

    return View(model);
}

このあたり、CakePHP の Model::$belongsTo と同じような使い方ができます。というか、同じようにするために外部キー/アソシエーション/ナビゲーションを利用する訳です。

実行するとすんなり動きます。

こんな訳で、1段階のリレーションは簡単にできるのですが、さて、4つのリレーションの場合はどうするんでしょうねえ?

カテゴリー: ASP.NET, Wordpress | ASP.NET MVC から wordpress のデータを扱う(3) はコメントを受け付けていません

ASP.NET MVC から wordpress のデータを扱う(2)

次に最近投稿した一覧を表示させます。
今回は、コントローラーの PostsController::RecentPosts メソッドから作っていきますね。

■コントローラーを LINQ で作る。

public ActionResult Recent()
{
    int max = 5;
    wordpressEntities ent = new wordpressEntities();
    var model = (from p in ent.wp_posts
                    join u in ent.wp_users on p.post_author equals u.ID
                    where p.post_status == "publish"
                    orderby p.post_date descending
                    select new
                    { Post = p, User = u }
                    ).Take(max);

    return View(model);
}

投稿した人の名前(User.display_name)を表示させたいので、wp_users テーブルとリンクさせます。そていて、最初の5行だけ表示させたいので Take 関数を使っています。
このための専用のモデルを作るのは面倒なので、無名クラスを作って new { Post = p, User = u } として作成しています。

こうすると、ビュー側で

  • item.Post.post_author
  • item.User.display_name

のように参照ができるハズですね(実は、うまくいきません…)。

■ビューを作る

<%@ Page Title=&quot;&quot; Language=&quot;C#&quot; MasterPageFile=&quot;~/Views/Shared/Site.Master&quot; Inherits=&quot;System.Web.Mvc.ViewPage<dynamic>&quot; %>

<asp:Content ID=&quot;Content1&quot; ContentPlaceHolderID=&quot;TitleContent&quot; runat=&quot;server&quot;>
	Recent
</asp:Content>

<asp:Content ID=&quot;Content2&quot; ContentPlaceHolderID=&quot;MainContent&quot; runat=&quot;server&quot;>

    <h2>最近投稿した記事</h2>
    <table>
        <tr>
            <th>ID</th>
            <th>post_date</th>
            <th>post_title</th>
            <th>post_author</th>
            <th>User.display_name</th>
            <th>guid</th>
        </tr>
        <% foreach (var item in Model) { %>
            <tr>
                <td><%: item.Post.ID %></td> 
                <td><%: item.Post.post_title %></td>
                <td><%: item.Post.post_date %></td>
                <td><%: item.Post.post_author %></td>
                <td><%: item.User.display_name %></td>
                <td><%: item.Post.guid %></td>
            </tr>
        <% } %>
    </table>
    <h2>最近投稿した記事(リンク版)</h2>
    <ul>
        <% foreach (var item in Model) { %>
        <li>
            <a href=&quot;<%: item.Post.guid %>&quot;><%: item.Post.post_title %></a>
            by <%: item.User.display_name %>
            at <%: item.Post.post_date %>
        </li>
        <% } %>
    </ul>
</asp:Content>

型無しの指定なので、ViewPage のままです。
それぞれの表示は、そんまま aspx に記述するとタイピングミスが起こるので、あらかじめコントローラー内でコーディングしたものをコピー&ペーストしました。

コントローラーで、

foreach (var item in model)
{
    Debug.Print("{0},{1}", 
        item.Post.ID, 
        item.Post.post_date,
        item.Post.post_author,
        item.User.display_name,
        item.Post.guid );
}

のように作っていくと、インテリセンスが使えて便利です。

さて、これを実行するとですね。。。実行エラーが発生するのです。

なんででしょうね?無名クラスで定義したものが、ビューのところではうまく取得できないようです。

■仕方がないのでモデルクラスを作る

という訳で、仕方がないので Posts モデルクラスを作ります。先に作った FindPosts メソッドも踏まえて、こんな感じです。

public class Posts : wp_posts 
{
    public wp_users User { get; set; }
    public wp_posts Post { get; set; }

    public IQueryable<wp_posts> FindPosts()
    {
        wordpressEntities ent = new wordpressEntities();
        var model = from t in ent.wp_posts
                    where t.post_status == &quot;publish&quot;
                    select t;
        return model;
    }
    public List<Posts> RecentPosts(int max)
    {
        wordpressEntities ent = new wordpressEntities();
        var model = (from p in ent.wp_posts
                        join u in ent.wp_users on p.post_author equals u.ID
                        where p.post_status == &quot;publish&quot;
                        orderby p.post_date descending
                        select new { Post = p, User = u }
                        ).Take(max);

        List<Posts> posts = new List<Posts>();
        foreach (var item in model)
        {
            posts.Add( 
                new Posts { Post = item.Post, User = item.User });
        }
        return posts;
    }
}

プロパティで wp_posts と wp_users のテーブルが参照できるように Post, User を定義しておきます。後は、コントローラーで作成していた LINQ の文をコピー&ペーストして、この結果を List に詰めて返します。

対して、コントローラーの RecentPosts メソッドは非常になるのでが、なんだかなぁ、という感じですね。

public ActionResult Recent()
{
    int max = 5;
    Models.Posts post = new Models.Posts();
    var model = post.RecentPosts(max);
    return View(model);
}

これを動かすと、ビューはそのままで綺麗に動きます。

一応、動くには動くのですが、wp_posts と wp_users のテーブルをいちいち join させないといけないのが CakePHP よりも劣っていますよね。

というのは、EDM(Entitiy Data Model)のところでリレーションを設定していないのが原因なのです。CakePHP では Model::$belongsTo などで join を指定しているのですが、wordpress のテーブルから引っ張ってきたテーブルでは、この join が設定されていません。

なので、お次は、リレーションを設定したうえで、最近投稿した一覧を表示させてみます。

カテゴリー: ASP.NET, Wordpress | 1件のコメント

ASP.NET MVC から wordpress のデータを扱う(1)

という訳で、今度は ASP.NET MVC から wordpress のデータを扱っていきます。これで CakePHP と比較すると、ASP.NET MVC が何処まで優れている/劣っているのかが分かるかと。

CakePHP から wordpress のデータを扱う | Moonmile Solutions Blog
www.moonmile.net/blog/archives/1888
CakePHP から wordpress のデータを扱う(2) | Moonmile Solutions Blog
www.moonmile.net/blog/archives/1896
CakePHP から wordpress のデータを扱う(3) | Moonmile Solutions Blog
www.moonmile.net/blog/archives/1922

wordpress のデータベースは SQL Server には対応していないので、テーブルの作成とインポートは別途行っています。実は、このあたり苦労したので…後で情報として残しておきますね。

ASP.NET MVC アプリの場合は LINQ to Entities を使うと楽なので、ズルをしてこれを使います。モデルの作成が楽というのがあるのですが。LINQ が使えるというのがミソでして。

■ビューを作る

ASP.NET MVC のビュー作成は、EDM(Entity Data Model)を使うと自動生成してくれるので、これを利用します。wp_posts クラスを指定します。

<%@ Page Title=&quot;&quot; Language=&quot;C#&quot; MasterPageFile=&quot;~/Views/Shared/Site.Master&quot; Inherits=&quot;System.Web.Mvc.ViewPage<IEnumerable<MvcWordpress.wp_posts>>&quot; %>

<asp:Content ID=&quot;Content1&quot; ContentPlaceHolderID=&quot;TitleContent&quot; runat=&quot;server&quot;>
	Index
</asp:Content>

<asp:Content ID=&quot;Content2&quot; ContentPlaceHolderID=&quot;MainContent&quot; runat=&quot;server&quot;>

    <h2>Index</h2>

    <table>
        <tr>
            <th>
                ID
            </th>
            <th>
                post_author
            </th>
            <th>
                post_date
            </th>
            <th>
                post_title
            </th>
            <th>
                post_category
            </th>
            <th>
                guid
            </th>
        </tr>

    <% foreach (var item in Model) { %>
    
        <tr>
            <td>
                <%: item.ID %>
            </td>
            <td>
                <%: item.post_author %>
            </td>
            <td>
                <%: String.Format(&quot;{0:g}&quot;, item.post_date) %>
            </td>
            <td>
                <%: item.post_title %>
            </td>
            <td>
                <%: item.post_category %>
            </td>
            <td>
                <%: item.guid %>
            </td>
        </tr>
    
    <% } %>

    </table>

</asp:Content>

■コントローラーを作る

EDM の場合、クラス名を変更できない(と思う)ので、「wp_」のようなプレフィクスはそのまま残ってしまいますね。これは、CakePHP のほうが prefix の設定ができる分、有利です。
更に云えば、T100,T200,T300 のような管理しやすいが他人には分かりにくいテーブル名の場合は、そのままクラス名として使われてしまいますね。CakePHP の Model はテーブル名を変更できるので、これも CakePHP のほうがよいかなと。

public class PostsController : Controller
{
    //
    // GET: /Post/

    public ActionResult Index()
    {
        wordpressEntities ent = new wordpressEntities();
        var model = from t in ent.wp_posts
                    where t.post_status == "publish"
                    select t;
        return View(model);
    }
}

ASP.NET MVC では、PostsController クラスで post_status をチェックしています。これは、EDM のクラスにはメソッドが追加できない(と思う)ので、findPosts メソッドを追加できないからなんですよねぇ。多分、拡張メソッドを使ってメソッドを追加するんでしょうが、単純にメソッドを追加、とはいかないようです。

■モデルは EDM そのままで

モデルは CakePHP と同様に EDM そのものを使います。wp_posts だけを返すのであれば、これで十分です。

■モデルのラッパークラスを作ってみる

ASP.NET MVC では、コントローラーでデータ加工をすることが多いので(EDMをモデルとして直接使う場合は特に)、コントローラーの各メソッドでクエリを書きます。
が、post_status = ‘publish’ のような、フラグ(他にも削除フラグとかなんとか)を、いちいちコントローラーでチェックするのは抵抗があるし、間違いも発生しやすいものです。
なので、wp_posts モデルをラップした Post クラスを作って CakePHP の時のように findPosts メソッドを作ってみます。

public class Post : wp_posts
{
    public IQueryable<wp_posts> FindPosts()
    {
        wordpressEntities ent = new wordpressEntities();
        var model = from t in ent.wp_posts
                    where t.post_status == &quot;publish&quot;
                    select t;
        return model;
    }
}

単純ですが、wp_posts クラスを継承したクラスを作ります。この継承が意味があるかどうかは別として、ひとまず wp_posts と同じものを指しているというつもりです。

コントローラーの Index メソッドは Posts::FindPosts メソッドを使って次のように書きます。

public ActionResult Index()
{
    Models.Post post = new Models.Post();
    var model = post.FindPosts();
    return View(model);
}

まあ、こんな風にしたほうが分かりやすいかな、と。
本来は、wp_posts.FindPosts() な形で書きたいですね。

お次は、最近投稿した記事一覧を表示させます。

カテゴリー: ASP.NET, Wordpress | 1件のコメント

CakePHP から wordpress のデータを扱う(3)

今度は wordpress のカテゴリ一覧、と指定したカテゴリ内の記事を拾ってみます。

wordpress はカテゴリの扱いがややこしくて、カテゴリの名前自体は wp_terms に入っているのですが…その後の wp_posts テーブルまでの関係がややこしいという。
# 更に云えば、カスタムフィールドと関係を見ていくと、なにやら大変という感じなのです。

  • terms: カテゴリ名などの名称
  • term_taxonomy: terms の分類(category, link_category など)
  • term_relationships: カテゴリとの親子関係
  • posts: 記事自体

という 4 つのテーブルが関わってきます。

いきなりはややこしいので、まずはカテゴリ一覧。

■カテゴリ一覧のビュー(views/categories/index.ctp)

<h2>カテゴリ一覧</h2>
<table>
	<tr>
		<th>term_id</th>
		<th>term_taxonomy_id</th>
		<th>name</th>
		<th>slug</th>
	</tr>
	<?php foreach($Categories as $item) : ?>
	<tr>
		<td><?php echo $item['Term']['term_id']; ?>
		<td><?php echo $item['TermTaxonomy']['term_taxonomy_id']; ?>
		<td><?php echo $item['Term']['name']; ?>
		<td><?php echo $item['Term']['slug']; ?>
	</tr>
	<?php endforeach ; ?>
</table>
<h2>カテゴリ一覧(リンク版)</h2>
<ul>
	<?php foreach($Categories as $item) : ?>
	<li>
		<a href=&quot;http://localhost/cake_wp/categories/item/<?php echo $item['Term']['slug']; ?>&quot;><?php echo $item['Term']['name']; ?></a>
	</li>
	<?php endforeach ; ?>
</ul>

カテゴリだけを表示したいので、Term と TermTaxonomy モデルだけを使います。

■コントローラー(controllers/categories_controller.php)

<?php
class CategoriesController extends AppController {

	var $name = 'Categories';
	var $uses = array('Category');
	function index() {
		$this->set('Categories',$this->Category->findCategories());
	}
	function item($slug='') {
		$this->set('Categories',$this->Category->findPosts($slug));
		$this->set('ViewData',array(
			'slug'=>$slug,
			'name'=> $this->Category->getCategoryName($slug)
				));
	}
}
?>

(追記 2014/06/22 item function を追加)
コントローラーは CategoriesController にしました。4 つのテーブルを使うので、どれの名前も適切ではないんですよね。そして、一覧表を取る時には index メソッドを作っています。

さて、モデルはどうするかというと、Term モデル、TermTaxonomy モデルを直接利用することは避けました。先にも書きましたがデータベース上は正規化されていることが常な訳で、そのまま O/R マッピングをしてしまうと、オブジェクト指向の世界では「現実離れ」なクラスになってしまいます。なので、より自然な形に戻す(本来の形に戻す)ために、Category モデルクラスを作ります。
ここでリストを取得するために、findPosts というメソッドを新たに作っています。
# find(‘all’) とか find(‘first’) とか合わせたほうがよいのですが、ひとまず。

■モデル(models/category.php)

さて、Category モデルは実テーブルとは連結していないので、$useTable = false にします。で、問題なのは、どうやってデータを取ってくるのか?ですね。

先に Term と TermTaxonomy モデルのアソシエーションを設定します。

<!-- models/term.php -->
<?php
class Term extends AppModel
{
	var $name = 'Term';
	var $primaryKey = 'term_id';
	var $hasOne = array(
		'TermTaxonomy' => array(
			'className' => 'TermTaxonomy',
			'foreignKey' => 'term_taxonomy_id' ));
}
?>

Term.term_taxonomy_id (1)-(1) TermTaxonomy.term_taxonomy_id の関係になるので Model::$hasOne を指定。

<!-- models/term_taxonomy.php -->
<?php
class TermTaxonomy extends AppModel
{
	var $name = 'TermTaxonomy';
	var $useTable = 'term_taxonomy';
	var $primaryKey = 'term_taxonomy_id';
	var $hasOne = array(
	    'Term' => array(
			'className' => 'Term',
			'foreignKey' => 'term_id' ));
}
?>

逆向きも同じで Model::$hasOne を指定。

こうしておくと、次のように findCategories が書けます。

<!-- models/category.php -->
<?php
class Category extends AppModel
{
	var $name = 'Category';
	var $useTable = false ;
	function findCategories() {
		if (0) {
			$TermTaxonomy = ClassRegistry::init('TermTaxonomy');
			$items = $TermTaxonomy->find('all',array(
				'conditions'=>&quot;taxonomy = 'category'&quot;));
			return $items ;
		} else {
			$Term = ClassRegistry::init('Term');
			$items = $Term->find('all', array(
				'conditions'=>&quot;TermTaxonomy.taxonomy = 'category'&quot;));
			return $items ;
		}
	}
}

if 文で分けていますが、どちらも同じ記述です。
テーブルに連結していない場合は、ClassRegistry::init メソッドでモデルクラスを読み込みます。この後は、普通に検索をして OK です。
TermTaxonomy モデルを中心にした場合は、’category’ を直接検索できますが、Term モデルを中心にした場合は、TermTaxonomy.taxonomy な形で conditions を設定します。

これはどちらも結果が同じになります。

[img 20110201_07.jpg]

今度は、カテゴリ内の記事一覧を取得します。

■カテゴリ内記事のビュー(views/categories/item.ctp)

<h2>カテゴリ内の記事一覧</h2>
<?php echo $ViewData['slug']; ?> /
<?php echo $ViewData['name']; ?>
<table>
	<tr>
		<th>Post.ID</th>
		<th>Post.post_title</th>
		<th>Post.post_date</th>
		<th>Post.guid</th>
	</tr>
	<?php foreach($Categories as $item) : ?>
	<tr>
		<td><?php echo $item['Post']['ID']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['post_date']; ?>
		<td><?php echo $item['Post']['guid']; ?>
	</tr>
	<?php endforeach ; ?>
</table>

な感じで、Post モデルから情報を取ってきます。カテゴリのスラッグ(名前)と日本語名称は、$ViewData という変数に入れておきます。

■まずは、コントローラーをクエリで書いてしまう。

面倒なので、Category::findPosts メソッドの中身をクエリで書いてしまったのがこれです。

<!-- models/category.php -->
<?php
class Category extends AppModel
{
	var $name = 'Category';
	var $useTable = false ;
	function findCategories() {
		if (0) {
		$TermTaxonomy = ClassRegistry::init('TermTaxonomy');
		$items = $TermTaxonomy->find('all',array(
			'conditions'=>&quot;taxonomy = 'category'&quot;));
		return $items ;
		} else {
		$Term = ClassRegistry::init('Term');
		$items = $Term->find('all', array(
			'conditions'=>&quot;TermTaxonomy.taxonomy = 'category'&quot;));
		return $items ;
		}
	}

	// カテゴリ内の記事一覧を取得する
	function findPosts($slug,$max=10) {
	if(0) {

		$sql = <<< HERE
select *
from wp_term_taxonomy as TermTaxonomy
 inner join wp_term_relationships as TermRelationship
 on TermTaxonomy.term_taxonomy_id = TermRelationship.term_taxonomy_id
 inner join wp_terms as Term
  on ( Term.term_id = TermTaxonomy.term_id )
 inner join wp_posts as Post
  on ( TermRelationship.object_id = Post.ID )
  where TermTaxonomy.taxonomy = 'category'
   and Term.slug = '$slug'
 order by Post.post_date desc
 limit $max
 ;
HERE;
	return $this->query($sql);
	} else {
		$Post = ClassRegistry::init('Post');
		$items = $Post->find('all',array(
			'conditions'=>&quot;TermTaxonomy.taxonomy = 'category'&quot;));
		return $items ;
	}

	}

	function getCategoryName($slug) {
		$Term = ClassRegistry::init('Term');
		$item = $Term->find('first',array(
			'conditions'=>&quot;slug = '$slug'&quot;));
		return $item['Term']['name'];
	}
}
?>

これでも用は足りるのですが、プレフィクス「wp_」が、そのまま出てきてしまうので難点が多いですよね。ただ、複雑なアソシエーション(外部キー)で悩むよりは、このほうが安全だと思います。

■アソシエーションで設定してみる。

が、CakePHP なのでアソシエーションで頑張ってみようとモデルを作ります。結論から言うと、4 つのテーブルを連携させて動かすことができません…果たして動くのか動かないのか分からないのですが、一応、ソースを晒しておきます。

TermRelationship モデルクラスはプライマリーキーを2つ持つので、$primaryKey に対して array で指定しているのですが、これでいいんでしょうかねぇ?

<!-- models/term_relationship.php -->
<?php
class TermRelationship extends AppModel
{
	var $name = 'TermRelationship';
	var $primaryKey = array('object_id','term_taxonomy_id');
	var $belongsTo = array(
		'TermTaxonomy' => array(
			'className' => 'TermTaxonomy',
			'conditions' => 'TermTaxonomy.term_taxonomy_id = TermRelationship.term_taxonomy_id'));
	var $hasOne = array(
		'Post' => array(
			'className' => 'Post',
			'conditions' => 'Post.ID = TermRelationship.object_id'));
}
?>

Post モデルに対しては、Model::$hasOne を使い、TermTaxonomy モデルに対しては Model::$belongsTo なんですが。
Post モデルに関しては、TermRelationship への連携を Model::$hasOne で指定しておきます。

<!-- models/post.php -->
<?php
class Post extends AppModel
{
	var $name = 'Post';

	var $belongsTo = array(
	    'User' => array(
			'className' => 'User',
			'foreignKey' => 'post_author' ));
	var $hasOne = array(
		'TermRelationship' => array(
			'className' => 'TermRelationship',
			'foreignKey' => 'object_id'));
}

これを有効にして、Category::findPosts メソッドを書き換えます。

	// カテゴリ内の記事一覧を取得する
	function findPosts($slug,$max=10) {
		$TermTaxonomy = ClassRegistry::init('TermTaxonomy');
		$items = $TermTaxonomy->find('all',array(
			'conditions'=> array(
				'TermTaxonomy.taxonomy' => 'category',
				'Post.post_status' => 'publish' )));
		return $items ;
	}

これを動かしてみるのですが、SQL文でエラーが出ます。

SELECT
 `TermTaxonomy`.`term_taxonomy_id`,
 `TermTaxonomy`.`term_id`,
 `TermTaxonomy`.`taxonomy`,
 `TermTaxonomy`.`description`,
 `TermTaxonomy`.`parent`,
 `TermTaxonomy`.`count`,
 `Post`.`ID`,
 `Post`.`post_author`,
 `Post`.`post_date`,
 `Post`.`post_date_gmt`,
 `Post`.`post_content`,
 `Post`.`post_title`,
 `Post`.`post_excerpt`,
 `Post`.`post_status`,
 `Post`.`comment_status`,
 `Post`.`ping_status`,
 `Post`.`post_password`,
 `Post`.`post_name`,
 `Post`.`to_ping`,
 `Post`.`pinged`,
 `Post`.`post_modified`,
 `Post`.`post_modified_gmt`,
 `Post`.`post_content_filtered`,
 `Post`.`post_parent`,
 `Post`.`guid`,
 `Post`.`menu_order`,
 `Post`.`post_type`,
 `Post`.`post_mime_type`,
 `Post`.`comment_count`,
 `Term`.`term_id`,
 `Term`.`name`,
 `Term`.`slug`,
 `Term`.`term_group`
FROM
 `wp_term_taxonomy` AS `TermTaxonomy`
   LEFT JOIN `wp_posts` AS `Post`
    ON (`TermRelationship`.`object_id` = `Post`.`ID` AND `TermTaxonomy`.`post_id` = `Post`.`id`)
   LEFT JOIN `wp_terms` AS `Term`
    ON (`Term`.`term_id` = `TermTaxonomy`.`term_taxonomy_id`)
WHERE
 `TermTaxonomy`.`taxonomy` = 'category'
AND `Post`.`post_status` = 'publish'

`TermRelationship`.`object_id` が無いとのことなので、TermRelationship の定義が悪いのですが、ちょっとよくわかりません。TermRelationship モデル自体は、プライマリキーが object_id と term_taxonomy_id の 2 つになるので、これが原因でしょう。

後でもう少し調べてみますが、この手の複雑なテーブルの場合はクエリ文が楽かなと思ってしまいます。まあ、作業量を考えると、CakePHP に準拠してないテーブルを扱う時は(wordpress は、準拠していると思うのだけど)クエリのほうが作成が速いってのがありますね。

カテゴリー: CakePHP, Wordpress | 6件のコメント

写真から固定比で画像を切り取るツールを探す

求ムッ!!! 等倍で切り取るツール…を探していた訳ですが、結構身近にありました。
# Photoshop とかあればいいんですけど、この手のデザインツールは手元にないのです。

無料写真編集ソフト フォトギャラリー – Windows Live on MSN
windowslive.jp.msn.com/photo.htm

去年あたりに新しくなった Windows Live Essential 2011 というのがありまして、この中のフォトギャラリーが要件を満たしてくれます。無料だし、Windows 環境だしという訳で。

指定の画像をダブルクリックした後に、

トリミングをクリック。

縦横比を正方形で選択。他もできそうです。

大きさを決めた後に再びトリミングのボタンを押せば完了。

これをぽちぽちとやって、200枚を2時間というスピードで、まぁなんとかという訳ですね。保存のところでちょっと時間が掛かるから、もう少し効率化したい訳ですが、美女Linux ハートマーク版の場合、顔が入らないといけないから自動って訳にはいきませんがな。

画像は 160×160 に揃えるので、後からツールで揃えます。

IrfanView – Official Homepage – one of the most popular viewers worldwide
www.irfanview.com/
IrfanView 日本語版のページ
www8.plala.or.jp/kusutaku/iview/

で揃えてしまうか、まぁ、自前のツールを使うかといったところですね。irfanView だと複数ファイルもいけるので、それで十分かと。自前のツールを使う場合は perl でバッチファイルを書いて何度も動かすときに便利、って具合です。

実は irfanView にも固定比で取れるのですが、結構面倒なんですよね。さすがに 200 枚という多さの場合には、ちょっと無理ってな感じなのでした。

カテゴリー: ツール | 写真から固定比で画像を切り取るツールを探す はコメントを受け付けていません

CakePHP から wordpress のデータを扱う(2)

今度は、wordpress のコメント表示

CakePHP から wordpress のデータを扱う | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1888

のところで記事の表示ができたので、これを流用します。

アクセスは http://servername/comments/recent のようにして、最近のコメントを取得。

■ビューを作る

ひな型が出来ているので、今度はいきなりビューを作ってしまいます。
モデル名やテーブルのカラム名なんかは想定で作ってしまって、後で合わせるってことができますね。テーブル仕様書ができていれば、これで分業ができるはずです。

<h2>最近投稿されたコメント</h2>
<table>
	<tr>
		<th>comment_ID</th>
		<th>comment_post_ID</th>
		<th>comment_author</th>
	</tr>
	<?php foreach($Comments as $item) : ?>
	<tr>
		<td><?php echo $item['Comment']['comment_ID']; ?>
		<td><?php echo $item['Comment']['comment_post_ID']; ?>
		<td><?php echo $item['Comment']['comment_author']; ?>
		<td><?php echo $item['Comment']['comment_author_url']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['guid']; ?>
	</tr>
	<?php endforeach ; ?>
</table>

<h2>最近投稿されたコメント(リンク版)</h2>
<ul>
	<?php foreach($Comments as $item) : ?>
		<li>
			<?php if ( $item['Comment']['comment_author_url'] == '' ) : ?>
				<?php echo $item['Comment']['comment_author']; ?>
			<?php else : ?>
				<a href=&quot;<?php echo $item['Comment']['comment_author_url']; ?>&quot;><?php echo $item['Comment']['comment_author']; ?></a>
			<?php endif; ?>
			on
			<a href=&quot;<?php echo $item['Post']['guid']; ?>&quot;>
			<?php echo $item['Post']['post_title']; ?>
			</a>
		</li>
	<?php endforeach ; ?>
</ul>

開発時の手順は、

  1. プログラマがテーブルタグで、必要な部品を揃えておく&チェックする。
  2. デザイナが、部品をコピー&ペーストしながらレイアウトを組む。

って感じですかね。<?php echo … ?>なところに値が入るってことだけ説明すれば後は CSS でデザインしてもらうとか。

# 実は Microsoft の Expression Design は、仮データを XML などで作っておいて、デザイン時にもデータが表示される、ってのが売りなんですが、、、これってあまり宣伝されていない気が。って、使っている人ってどのくらい?って感じなのですが。

■コントローラーを作る

コントローラーはあっさりと作りました。
まあ、全体用の index メソッドと最新のコメントを取る recent メソッドです。

<?php
class CommentsController extends AppController {

	var $name = 'Comments';
	function index() {
		$this->set('Comments',$this->Comment->find('all'));
	}
	function recent() {
		$this->set('Comments',$this->Comment->findRecentComments(5));
	}
}
?>

CakePHP の Model::find メソッドを使っていて気づいたのですが、web の場合、mysql の limit(行数制限)は必須かもしれませんね。デスクトップアプリケーションだと、間違って数万行を出してもアプリを止めれば接続が切れるのでサーバーのダウンが避けられますが、web 経由の接続だとあっという間にダウンしてしまいそう。なので、必ず limit を指定しておいて最初の数百行に抑えておくというのが安全だと思います。
# SQL Server や Oracle だと top コマンド

■モデルを作る

Comment.comment_post_ID (*)-(1) Post.ID の連携があるので、Model::$belongTo 変数に記述します。実際は inner join にしたいところですが、Comment の親には必ず Post があるので、結果は同じになります。

<!-- models/comment.php -->
<?php
class Comment extends AppModel
{
	var $name = 'Comment';

	var $belongsTo = array(
	    'Post' => array(
				'conditions' => array(
					array('Post.post_status'=>'publish'),
					array('Comment.comment_approved'=>'1')),
			'className' => 'Post',
			'foreignKey' => 'comment_post_ID' ));

	// 最近投稿した記事を取得する
	function findRecentComments($max = 5) {
		return parent::find('all', 
			array(
				'conditions' => array(
					array('Post.post_status'=>'publish'),
					array('Comment.comment_approved'=>'1')),
				'order'=>'comment_date desc',
                'limit'=>$max));
	}
}
?>

最近投稿されたコメントのほうは、最大値を指定して、日付を逆順にして表示。
あと、記事(Post)の方のステータスが変わった場合にも対処するために、Model::$belongsTo のほうに、conditions を追加しておきます。こうすると、left join の on のところに条件が追加されます。

■結果

これも結果が見れます。

http://wpcake.moonmile.net/comments/recent

と言う訳で、お次はカテゴリ一覧とカテゴリ内の表示、これが wordpress では、ややこしい部類なのです。

カテゴリー: CakePHP, Wordpress | CakePHP から wordpress のデータを扱う(2) はコメントを受け付けていません

CakePHP から wordpress のデータを扱う

再び CakePHP 本体に戻って、その実力なぞ…というか自分的に調査。

そういえば、v1.3.x からは Model::findAll メソッドが動かず Model::find(‘all’) に切り替えられたんだそうだ。

find :: データを取得する :: モデル :: CakePHPによる開発 :: マニュアル :: 1.3コレクション :: The Cookbook
http://book.cakephp.org/ja/view/1018/find

この変更は、ちと大きいのかなぁ。古いマニュアルを読んでいると findAll を使っているところが多いので、悩んだりします。因みに、findAllByカラム名 は動くんですよね(何故か、findAll だけが動かないという現象で悩んだ)

wordpress のデータベースの構造は比較的簡単で、11 個しかありません。詳しいところは、

Amazon.co.jp: PHPによるWordPressカスタマイズブック―2.8対応 テンプレートの改造からプラグインの作成まで: 藤本 壱: 本
[amazon http://www.amazon.co.jp/dp/4883376745]

を参照して貰うとして。2.8 ベースなのですが、私的にはこれで十分な状態。

  • wp_posts: 投稿記事/ページ
  • wp_comments: コメント
  • wp_users: ユーザー情報

あと、カテゴリやカスタムフィールド関係が

  • wp_terms
  • wp_term_taxonomy
  • wp_term_relationships

に入っています。

これらの関係を、CakePHP から扱うにはどうするのか?ってのを実践してみれば、実運用で使える技術が鍛えられるはず。

ひとまず、プレフィックスの「wp_」が邪魔なのですが、CakePHP のデータベースの設定で prefix を指定できるので、これを使います。

class DATABASE_CONFIG {
	var $default = array(
		'driver' => 'mysql',
		'persistent' => false,
		'host' => '127.0.0.1',
		'login' => 'manabook',
		'password' => 'manabook',
		'database' => 'manabook',
		'prefix' => 'wp_',	★
	);

こうしておくと、テーブル名が「wp_posts」でも Post モデルとして参照できます。

■最初はURLを入れる

CakePHP はマニュアル無しでサイトを作れるのか? | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/1753 

自分でやったことを、もう一度試してみるるので、最初は url を決めます。
http://servername/posts

と入力します。

■コントローラー作る

当然エラーが出て、コントローラーが無いとのお達しなので、PostsController を作ります。

<?php
class PostsController extends AppController {

	var $name = 'Posts';
	// ひとまず一覧
	function index() {
		$this->set('Posts',$this->Post->findPublish());
	}
	// 最近の一覧
	function recent() {
		$this->set('Posts',$this->Post->findRecentPosts(5));
	}
}
?>

ここで findPublish メソッドと findRecnetPosts メソッドを作っていますが、実は wp_posts は、公開(post_status)の状態があって、一覧の場合はこのカラムを必ずみることになります。
この条件をコントローラーで指定してもよいのですが、ここは Post モデルに任せることにします。

■モデルを作る

色々試行錯誤した結果、こんな感じになります。

<!-- models/post.php -->
<?php
class Post extends AppModel
{
	var $name = 'Post';

	var $belongsTo = array(
	    'User' => array(
			'className' => 'User',
			'foreignKey' => 'post_author' ));

	function findPublish() {
		return parent::find('all',
			array('conditions' => array('Post.post_status'=>'publish'),
				  'order'=>'post_date'));
	}
	// 最近投稿した記事を取得する
	function findRecentPosts($max = 5) {
		return parent::find('all',
			array('conditions' => array('Post.post_status'=>'publish'),
				  'order'=>'post_date desc',
				  'limit'=>$max));
	}
}
?>

Model::find メソッドの記法がややこしいのですが、Post.post_status の $conditions を必ずいれておいて、後は order by するのを忘れないように。

Model::$belongsTo を指定しているのは、投稿したユーザー名(wp_user)にリンクさせるためです。

■ビューを作る

これを踏まえて、ざっくりとビューを作ると、

<table>
	<tr>
		<th>ID</th>
		<th>post_date</th>
		<th>post_title</th>
		<th>guid</th>
	</tr>
	<?php foreach($Posts as $item) : ?>
	<tr>
		<td><?php echo $item['Post']['ID']; ?>
		<td><?php echo $item['Post']['post_date']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['guid']; ?>
	</tr>
	<?php endforeach ; ?>
</table>

まあ、ひとまずはテーブル表示をするだけ。下記で実行できます。

http://wpcake.moonmile.net/posts

■最近投稿した記事も表示させる

wordpress のように最近投稿した記事(recent posts)も表示させてみます。

<h2>最近投稿した記事</h2>
<table>
	<tr>
		<th>ID</th>
		<th>post_date</th>
		<th>post_title</th>
	</tr>
	<?php foreach($Posts as $item) : ?>
	<tr>
		<td><?php echo $item['Post']['ID']; ?>
		<td><?php echo $item['Post']['post_date']; ?>
		<td><?php echo $item['Post']['post_title']; ?>
		<td><?php echo $item['Post']['post_author']; ?>
		<td><?php echo $item['User']['user_nicename']; ?>
		<td><?php echo $item['Post']['guid']; ?>

	</tr>
	<?php endforeach ; ?>
</table>
<h2>最近投稿した記事(リンク版)</h2>
<ul>
<?php foreach($Posts as $item) : ?>
	<li>
		<a href=&quot;<?php echo $item['Post']['guid']; ?>&quot;><?php echo $item['Post']['post_title']; ?></a> by <?php echo $item['User']['user_nicename']; ?> at <?php echo $item['Post']['post_date']; ?>
	</li>
<?php endforeach; ?>
</ul>

先にテーブルで値を確認した後に、ペタペタとリンクの表示を作っていきました。

下記で実行できます。

http://wpcake.moonmile.net/posts/recent

お次は、コメント一覧なぞ。

カテゴリー: CakePHP, Wordpress | 1件のコメント

写真にマスクをかけて切り抜きをする

久し振りに、美女Linux http://bijo-linux.com のブログパーツ作成です。
今回は、ブログパーツ自体をハートマークにしてしまおうという企画で、出来上がりはこんな感じ。

ここのブログだと、背景が白なので変わり映えしませんが、色がついていたり画像が貼ってあったりすると、透過されているのがよくわかります。

さて、これをどう実現するのかと言うと、って非常に簡単で。

  • ハートマークのフレームを用意します。
  • ハートマークに切り抜いた写真を用意します。
  • そして重ね合わせ

しているだけです。普通、写真のほうは四角形だったりするので、ブログパーツ自身も四角形になってしまうことが多いのですが、元の写真から切り抜いてしまえば、重ね合わせのときに両方とも背景が投下されます。

▼切り抜いた写真

▼フレーム画像

の重ね合わせです。

さて、この切り抜いた画像をどう作るかというと、マスク画像との重ね合わせになるわけですが、

▼元の写真

▼マスク画像

を使います。これを使って、ちまちま画像ツールでやってもいいんですが…ちょっとそれは無理ッ!!! なので、マスクするだけのツールを作りました。

public class ImageOverlay
{
    public bool OverlayMask(string photofile, string maskfile, string outfile)
    {
        try
        {
            Bitmap photo = new Bitmap(photofile);
            Bitmap mask = new Bitmap(maskfile);
            Bitmap output = this.OverlayMask(photo, mask);
            if (output == null)
            {
                return false;
            }
            output.Save(outfile);
            return true;
        }
        catch
        {
            Console.WriteLine(&quot;can't make overlay file&quot;); 
            return false;
        }
    }
    public Bitmap OverlayMask(Bitmap bmpPhoto, Bitmap bmpMask)
    {
        int width = bmpPhoto.Width;
        int height = bmpPhoto.Height;
        Bitmap bmpOutput = new Bitmap(width, height, PixelFormat.Format32bppArgb);

        for (int y = 0; y < width; y++)
        {
            for (int x = 0; x < height; x++)
            {
                Color photo = bmpPhoto.GetPixel(x, y);
                byte red = bmpMask.GetPixel(x, y).R;
                byte alpha = (byte)(255 - red);
                Color mask = Color.FromArgb(alpha, photo);
                bmpOutput.SetPixel(x, y, mask);
            }
        }
        return bmpOutput;
    }
}
  • photo: 元画像
  • mask: マスク画像
  • output: 出力画像

ってな訳で、ちまちまと1ドットずつ重ね合わせ…って、なんかいい方法ないですかね?

ImageAttributes クラス (System.Drawing.Imaging)
http://msdn.microsoft.com/ja-jp/library/h4f66d59.aspx

あたりで、alpha チャンネルを変えるだけでいいと思うだけど、まあ、これでもそれなりのスピードが出ているので良しとしますか(画像が大きくなるとちょっとアレなんでしょうが)。

カテゴリー: 開発, ブログパーツ | 写真にマスクをかけて切り抜きをする はコメントを受け付けていません

クローリングされにくいユニークなIDを発行する方法

ちょっと思いつきメモ。

CakePHP を使っていて思うのだけど、id が大抵 auto inrement になっているのが問題になる場合がある。内部的な id ならば int でガンガン連携すればいいのだけど、

  • レガシーデータベースの場合は、id が文字列だったり
  • レガシーデータベースの場合は、プライマリーキーが複数だったり、
  • 連続する id だと、クローリングされやすい。

という問題がある。

先の2つは、後で CakePHP で確認するとして、連続する id で問題なのは、

  • 商品IDのように、サイトでクローリングされるとちょっと困る

場合なのだ。

例えば http://servername/books/100 という id を振ってあったら、次は容易に http://servername/books/101 と想像できる。で、そうなると、books/10000 なんてあったら、すぐにでも perl なりでクローリングができてしまうという…まあ、ブログなんかはいいんだけど(検索されるのが目的だし)、商品サイトなんかは良し悪しといところがある。

で、文字列なり数値で別なユニークなキーが欲しいときは、

m: ある程度大きい素数
n: m よりも大きく、m で割り切れない数

にしておいて、

id = (m * i) mod n
0 <= i <= n

とすると id が取得できる、ってこれであっているのかな?(他に良い方法がありそうだけど)

証明は、

  1. n > m * i の間は、id は任意になる。
  2. n < m * i+1 となる時、id = m*(i+1)-n = k とすると、k は m を割り切れない(mが素数だから)
  3. 初期値の s0 = k – m は、i が n になるまで一意になるので、id は結果的に一意になる。

…って、きちんとした証明になっていないので、こっちは後程。

カテゴリー: 開発, 雑談 | クローリングされにくいユニークなIDを発行する方法 はコメントを受け付けていません