2014年12月23日火曜日

Capistrano3 本番ビルドでdeploy:assets:precompileエラー。

Capistrano3を使ったrailsアプリのビルドで3時間くらいはまったのでメモ。

環境

  • rails(ruby2.1.0, rails4.1.6)
  • さくらvps

Capistrano3でビルドをすると、deploy:assets:precompileで必ずエラーになってしまう現象が発生した。
vagrantを使った開発環境では何の問題もなかった。

しばらく調査をしたところ、さくらvpsのsyslogにメモリが不足しているというログが見つかった。
どうやらvpsサーバーだと、rake assets:precompileでスワップメモリが不足し、失敗とみなされてしまうようだ。

解決方法は単純で、Capistrano3配置時にdeploy:assets:precompileを行わなければよい。
rake assets:precompileはローカルで行えばよい。

Capistrano3でdeploy:assets:precompileを実行しないようにするには、deploy.rbを以下のように変更する。


namespace :deploy do

  namespace :assets do

    Rake::Task['deploy:assets:precompile'].clear_actions
    
    desc "Precompile assets"
    task :precompile do
      puts "-----nothing-------"
    end
  end

end

Rake::Task['deploy:assets:precompile'].clear_actionsを書き忘れてしばらくはまってしまいました。ご注意を。

それにしてもcapistranoは学習コストが高過ぎると思うのは、私だけだろうか。
結局ソースコードを読まないと解決できないということが多すぎる気がする。


山情報&登山SNSアプリ「マウンテンチャンネル」を公開しました。よろしくです!

マウンテンチャンネル公式サイト

Android app on Google Play
参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年12月17日水曜日

swift基礎 Alamofireでhttp接続

swiftの基礎まとめ。
HTTP接続でAPIからデータ取得する実装です。Alamofireライブラリを利用してみます。

環境

  • Xcode Version 6.1 (6A1052d)

AlamofireのDL

CocoaPodsで管理できないので、gitで直接取得します。


// プロジェクトフォルダへ移動
cd /Users/{username}/Documents/swiftworkspace/projectfolder

// サブモジュールとして取得する
git submodule add https://github.com/Alamofire/Alamofire.git

// xcode6.1でLink Binary With Libraries機能を利用して、Alamofire.frameworkを追加する。

上記の手順を踏むと、Libraryとして利用できるようになるので、公式サイトに説明されている通りに実装します。


import Alamofire

        Alamofire.request(.GET, "http://test/api/v1/tests", parameters: ["offset": 0, "limit": 1, "test_id": 1])
            .responseJSON { (request, response, JSON, error) in
                if (response?.statusCode == 200) {
                    println("success")
                    println(JSON)
                } else {
                    // error
                    println(error)
                }
        }

上記のように記述することで、APIを使ってjsonを取得することができます。

jsonのパースはSwiftyJSONが一般的なようです。SwiftyJSONの導入方法と使い方はまた別記事で。
以上

参照

この記事がお役にたちましたらシェアをお願いします

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

MT.CHANNELアプリ リリース

1週間ほど前に、MT.CHANNEL(マウンテン・チャンネル、マウントチャンネル)というandroidアプリをリリースしました。

山情報と登山用SNSを融合したアプリです。

このアプリを作成した目的は、自分自身の趣味でもある登山で利用できるアプリが欲しかったのと、最近登山者がやたらと増えているので登山専用のSNSアプリが欲しいと思ったからです。

ユーザー同士が山に関する情報交換をすることで、色々と山の事故なども減らせたらと思い作成しました。

まだデータは少ないですが、徐々に増やしていくので、来年の登山シーズンからでも是非ご利用ください。(もちろん今すぐ利用できるので、今年の真冬の登山からでも利用できます)

アプリ機能紹介

投稿は山単位で可能です。

地図や山の情報を確認できます。google mapと連携も可能です。

噴火警戒レベルが指定されている山では、噴火警戒レベルが表示されます。

ログインアカウント認証では、twitterを利用しています。

言語は日本語、英語、中国語の3ヶ国語が利用可能です。

twitter公式アカウント

twitter公式アカウントの作成をしました。山に関する気になるニュースや、アプリの更新情報についてお知らせします。

以上
アプリの宣伝でした。
でわ。

PS.
facebookページとか、その他のサイトをまだ用意していないので、 「こういう機能が欲しい!」とか「〇〇山のデータを作って!」みたいな要望はとりあえずこのblogのコメント欄にお願いします。

この記事がお役にたちましたらシェアをお願いします

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

2014年12月15日月曜日

git 過去のコミットにタグをつける

gitでタグをつけわすれたときに、過去に遡ってtagを付加する場合は以下の方法で行う。

ハッシュとコメントのログを閲覧


git log --pretty=oneline

上記で対象のハッシュを見つけたら、tagを付加する。


git tag release1.0.0 b7b

タグをリモートリポジトリにpushする。


git push origin --tags

以上

この記事がお役にたちましたらシェアをお願いします

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

2014年12月13日土曜日

swift基礎 storyboardなしでTableViewを実装

swiftの基礎まとめシリーズ。
アプリ作成には避けて通れないTableViewの使い方です。

環境

  • Xcode Version 6.1 (6A1052d)

tableviewの実装

前回と同じように、storyboardを使わない実装方法です。
といってもobjective-c版とほとんど変化はありません。


import UIKit

class TestsViewController: UITableViewController {
    
    // tableviewで表示する配列
    var tests = [Test]()
    
    // testdata load
    func test() {
        // Do any additional setup after loading the view, typically from a nib.
        // self.navigationItem.leftBarButtonItem = self.editButtonItem()
        
        //let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
        //self.navigationItem.rightBarButtonItem = addButton
        
        if tests.isEmpty {
            println("empty");
        } else {
            println("not empty");
        }
        
        var obj = Test(id: 1, testCode: "001", testName: "英語", imagePath: "asasa")
        tests.append(obj)
        
        var obj2 = Test(id: 2, testCode: "002", testName: "数学", imagePath: "asasa")
        tests.append(obj2)

        
        if tests.isEmpty {
            println("empty");
        } else {
            println(tests.count);
        }
        
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.test()
        tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier:"testcell")
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tests.count
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell:UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"testcell")
        let test = tests[indexPath.row] as Country
        
        cell.textLabel.text = test.testName
        return cell;
    }
    
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }
    
    
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let test = tests[indexPath.row]
        let resultViewController = ResultViewController()
        navigationController!.pushViewController(resultViewController, animated: true)
        
    }
    
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            //objects.removeObjectAtIndex(indexPath.row)
            //tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }

}


objective-cのテーブルviewより随分とスッキリした短いコードになっています。
一番のポイントは tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier:"testcell") です。
storyboardを使わない場合は、表示に利用するcellをviewDidLoadメソッドで登録しておく必要があります。カスタムセルを利用する場合も同様です。

参照

この記事がお役にたちましたらシェアをお願いします

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

2014年12月12日金曜日

android studio1.0.0の変更点

android studio1.0.0にupdateしたら色々と変更されていました。
なので、以下に記録を残しておきます。

環境

  • andoid studio 1.0.0
  • mac Yosemite

sdkのパスを移動する

まず最初に、sdkを移動させるように警告されます。
デフォルト配置の場所はsdkを配置する場所じゃないよ!のようなエラーが出ます。

私は以下のようにユーザーフォルダ以下に配置しました。


mv /Applications/Android\ Studio.app/sdk /Users/{username}/

sdkを移動して再起動すると、sdkのpathを聞かれるので配置した場所を設定します。
0.99 → 1.0.0の順番の更新になるので、画面の説明にしたがって更新を進めていきます。

また、途中でandroid studioが開かなくなることがあるかもしれません。
Android Studioに設定してあるjavaのバージョンがMacのデフォルト設定と異なっていて利用できないのが原因です。 なので、Info.plistの以下の部分を変更します。


☓ JVMVersion → 1.6*
◯ JVMVersion → 1.6+

上記のように指定すると、java1.6でなく、java1.6以上で起動するようになります。

gradleの設定を変更

gradleの設定が色々と変更されているので、おそらく変更が必要になると思います。
以下の属性名称を利用している場合は、変更が必要になります。


runProguard => minifyEnabled
zipAlign => zipAlignEnabled
jniDebugBuild => jniDebuggable
renderscriptDebug => renderscriptDebuggable

上記の左の属性を設定している場合は、右に変更してください。
以上です。

p.s jenkinsサーバーの設定も変更しないとダメかもしれません。jenkinsも動作しないようなら、また追記します。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年12月10日水曜日

swift基礎 storyBoardを利用しない画面遷移

swiftの基礎まとめ。
storyBoardを利用しないで画面遷移を実装する方法です。

環境

  • Xcode Version 6.1

最初のページへの画面遷移を作成する

AppDelegate.swiftに処理を記載します。 よくありがちなパターンとして、splash画面に遷移させます。splash画面は、SplashViewControllerというクラスにします。


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow!

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        window = UIWindow(frame: UIScreen.mainScreen().bounds)
        window.backgroundColor = UIColor.whiteColor()
        window.rootViewController = UINavigationController(rootViewController: SplashViewController())
        window.makeKeyAndVisible()
        return true
    }

}

windowオブジェクトをForced Unwrappingで宣言しています。
Optional Chainingでも実装可能ですが、エラーはコンパイルでつぶす方向が良いと思います。まあ、好みなのでどちらでも良いと思います。

次のページへの画面遷移を作成する

slash画面では、こにょごにょ処理をやって初期化とかデータを取得したらログイン画面に遷移するのもよくありがちなパターンですね。以下のような感じです。


class SplashViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // something doing
        self.mvLoginView()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func mvLoginView() {
        println("go loginView")
        let loginViewController = LoginViewController()
        navigationController!.pushViewController(loginViewController, animated: true)
    }
    
}

そのまんまの処理です。
mvLoginView関数を呼び出して次のログイン画面に遷移させています。letは定数宣言。直接実体化してもこのサンプルなら問題なし。
navigationControllerは、Forced UnwrappingでpushViewControllerを呼び出します。ここもOptional Chainingでも動きます。

printlnはデバッグ用の関数です。
swiftではデバッグにNSLogではなく、printlnが推奨されています。
NSLogより10倍くらい速いらしいです。

とりあえず画面遷移はこんな感じで記載していくとよいでしょう。swiftは本当にコード量が少なくてスッキリ書けますね。

参照

この記事がお役にたちましたらシェアをお願いします

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

2014年12月7日日曜日

jenkins git unable to unlink old

jenkinsでandroidアプリのビルドをしたら発生した。

  • jenkins

facebookのSDKをimportしたら発生。原因は最初のgit commitの時に、buildフォルダまでコミットしてしまって、 それ以降のbuild時もbuildフォルダを見てapkが作成されていたのに、最新のソースではbuildフォルダをdeleteしてcommitしたから。

対応方法は簡単で、エラーを起こしているフォルダを削除すれば良い。


// gitで利用しているworkspaceに移動
/var/lib/jenkins/workspace/{project}/libraries/facebook

// buildフォルダを削除
sudo rm -rf build

これでbuildしてやるといつもどおりbuildが走った。

参考

この記事がお役にたちましたらシェアをお願いします

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

2014年12月6日土曜日

エンジニアのための仕事用Gmail設定

ここ最近は、毎日の振り返りの他に、3ヶ月に1回くらい大きく仕組みづくりの見直しをしています。
おかげで仕事量は増えているのに、ミスも少なく、時間にも余裕ができ、勉強や趣味に時間を回せています。
結局、仕組みと習慣と考え方で人生が大きく変わるんだと実感してます。

環境

  • Gmail

1.ショートカットを有効に

設定でonに。

これまで無効にしてたけど、覚えることにした。

2.返信時のデフォルトの動作:

「全員に返信」に設定

仕事用のメールの9割以上が全員に返信なので、デフォルト設定に変更。個人のやりとりはチャットやSNSになってしまった。

3.重要マーク

「マークを表示しない」に設定

自動振り分けは役に立たなすぎるので解除

4.送信取り消し設定

Labで設定

zでキャンセル可能になる。

5. google IMEを導入

会社のmacに入れたので、自宅でも同期させた。便利。

6. 返信定型文を導入

仕事メールの返信パターンはだいたい同じなので導入した。

あとはテンプレやフィルターのチューニングは週に1,2くらいで行っています。参考までに。
以上

この記事がお役にたちましたらシェアをお願いします

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

vagrantでdockerを利用する

dockerを導入してみました。
最終的にはサーバーのCIを可能にすることですが、まずは基本から学習していきます。

環境

  • vagrant1.6.5

CoreOSをinstall

dockerを使うならCoreOSが良いとのことなので導入する


// vagrantのCoreOsを取得
git clone https://github.com/coreos/coreos-vagrant.git
cd coreos-vagrant

vagrantを起動してログインする


// 起動
vagrant up

// ログイン
vagrant ssh

ログインしたらdockerのバージョンを確認する


docker version
Client version: 1.3.2
Client API version: 1.15
Go version (client): go1.3.2
Git commit (client): 50b8feb
OS/Arch (client): linux/amd64
Server version: 1.3.2
Server API version: 1.15
Go version (server): go1.3.2
Git commit (server): 50b8feb

centosを取得


sudo docker pull centos

centos:latest: The image you are pulling has been verified
511136ea3c5a: Pull complete 
5b12ef8fd570: Pull complete 
34943839435d: Pull complete 
Status: Downloaded newer image for centos:latest

imagesを表示


sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
centos              latest              34943839435d        2 days ago          224 MB

コンテナを起動し、shellに入る


// -i → コンテナーの標準入力を開く。
// -t → ttyを確保する。
sudo docker run -it centos:latest /bin/bash

vimをinstallしてみる


// vimをinstall
yum install vim-enhanced

// 確認
which vim
/usr/bin/vim

コンテナから抜けて、コンテナの状態を確認する


// コンテナから抜ける
exit

// コンテナの状態を確認する
sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                          PORTS               NAMES
93cb268664fe        centos:latest       "/bin/bash"         33 minutes ago      Exited (0) About a minute ago                       sad_kowalevski      
49f86c04bc74        centos:latest       "/bin/bach"         34 minutes ago

コンテナから抜けて、コンテナの状態を確認する


// docker imageを作成する
sudo docker commit 49f86c04bc74 viminstall

imageが作成されていることを確認する


sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
viminstall          latest              4e047b5e2d39        16 seconds ago      224 MB
centos              latest              34943839435d        2 days ago          224 MB

上記で作成したDocker Imageを実行


sudo docker run -it viminstall

// 確認
which vim
/usr/bin/vim

vimが入っているのを確認

その他


dockerのコンテナを全て削除

docker rm `docker ps -a -q`

dockerのimageを削除

sudo docker rmi REPOSITORY

ひとまず概要は理解できました。今度はDockerfileとかDocker Hubを使ってみよう。

参考

この記事がお役にたちましたらシェアをお願いします

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

2014年12月5日金曜日

railsでtwitter風の時間表示を返す

  • ruby 2.1.2
  • rails 4.1.6
  • grape

アプリでユーザーの投稿時間を返す文字列をtwitter風にする実装のメモです。
「1時間前」とか、そういう表示のパターンですね。
結論から言うと、railsで用意されているAction View Date Helpersを使えば良いです。でも、今回はmodelでView Date Helpersを呼ぶ必要があったので、少し工夫が必要でした。
結局、以下のように呼び出しました。

 

    # coding: utf-8
    class TestModel < ActiveRecord::Base 
      include ActionView::Helpers::NumberHelper

      // something

      ApplicationController.helpers.time_ago_in_words(test.created_at)
    end

    

デフォルトだと英語なので、他の言語にしたい場合は引数にlocalを設定してあげればOK。

 

    # coding: utf-8
    class TestModel < ActiveRecord::Base 
      include ActionView::Helpers::NumberHelper

      // something

      ApplicationController.helpers.time_ago_in_words(test.created_at,{ :locale => 'ja' })
    end

    

便利ですねえ。やっぱvoltよりrailsのほうが良いですね。

参考

この記事がお役にたちましたらシェアをお願いします

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

server spec2

server spec2を導入したらrspecが3以上の対応になっていたのでコードを修正しました。

  • ruby 2.1.2
  • serverspec 2.3.1

変更前

 

    describe command('nginx -v') do
      it { should match /nginx version: nginx\/1\.6\.2/ }
    end

    

変更後

 

    describe command('nginx -v') do
      its(:stdout) { should match /nginx version: nginx\/1\.6\.2/ }
    end

    

公式サイトをみればわかりますが、ずいぶんと分かりやすくコードを書けるようになりました。ただ、rspc3の学習は必須です。
以上

この記事がお役にたちましたらシェアをお願いします

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

2014年11月23日日曜日

Nginx uninstall

環境

  • mac Yosemite
  • vagrant1.6.5(centos 6.5)

ググってもなかなかひっかからないのでメモ


sudo yum erase nginx

これだけです。
まあ確かにnginxを削除する機会は少ないですね。インフラのCIってまだあんなり一般的じゃないのかな。

この記事がお役にたちましたらシェアをお願いします

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

Ruby for chef

環境

  • ruby 2.1.2

chefをいじっているとrubyのコードを読まないといけないことが多々あります。
けど、普段のwebアプリやAPI開発ではあまり利用しないコードが結構見られます。
ということで良くchefの中で見る子コードの勉強。

カレントディレクトリを出力


puts ENV['PWD'] # /Users/{username}/Documents/study/ruby
puts Dir::pwd   # /Users/{username}/Documents/study/ruby

URLやパスを結合する


puts File.join("https://test.com", "name=yamada")  # https://test.com/name=yamada
puts File.join("https://test.com/", "name=yamada") # https://test.com/name=yamada

スラッシュが勝手に保管されます。

以上。また気になるコードを見つけたらのせます。

この記事がお役にたちましたらシェアをお願いします

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

2014年11月18日火曜日

ruby使いが手を動かして学ぶphp その3

前回の続き。今回はcakephp2と格闘

環境

  • mac Yosemite
  • vagrant1.6.5(centos 6.5)

アプリのパスを設定

アプリを動かすにはapp/Config/bootstrap.phpを修正する必要がある。

編集しなくてはいけない三つの定数は、 ROOT 、 APP_DIR 、 CAKE_CORE_INCLUDE_PATH

  • ROOT には、アプリのフォルダが含まれているディレクトリのパスを設定
  • APP_DIR には、(訳注:相対的な)アプリのフォルダ名を設定
  • CAKE_CORE_INCLUDE_PATH には、CakePHPライブラリフォルダのパスを設定


if (!defined('ROOT')) {
    define('ROOT', DS . 'var' . DS . 'www' . DS . 'html' . DS . 'test');
}

/**
 * The actual directory name for the "app".
 *
 */
if (!defined('APP_DIR')) {
        define('APP_DIR', 'test');
}

define(
    'CAKE_CORE_INCLUDE_PATH',
    ROOT . '/Vendor/cakephp/cakephp/lib'
);

Console/cakeを動作させるための設定


PHP Warning:  include(Cake/Console/ShellDispatcher.php): failed to open stream: No such file or directory in /var/www/html/test/test/Console/cake.php on line 43

上記のエラーが発生。このままでは動かないので、以下の設定を変える。
まずはConsole/cake.phpを修正する必要がある。


        $root = dirname(dirname(dirname(__FILE__)));
        $appDir = basename(dirname(dirname(__FILE__)));


        //$composerInstall = $root . DS . $appDir . DS . 'Vendor' . DS . 'cakephp' . DS . 'cakephp' . DS . 'lib';
  $composerInstall = $root . DS . 'Vendor' . DS . 'cakephp' . DS . 'cakephp' . DS . 'lib';

$appDirがあるので、これは不要。削除してやると動作する。
でもこのままではまだエラー。

さらに続きます。

この記事がお役にたちましたらシェアをお願いします

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

2014年11月17日月曜日

ruby使いが手を動かして学ぶphp2

前回の続き

環境

  • mac Yosemite
  • vagrant1.6.5(centos 6.5)

mysql起動


/etc/init.d/mysqld start

データベース作成


    CREATE DATABASE `cakephp`;

    GRANT ALL ON `cakephp`.* to test@localhost IDENTIFIED BY 'passtest';

    FLUSH PRIVILEGES;

apacheをinstall


// apache install
sudo yum -y install httpd

// launch
sudo /etc/init.d/httpd start

cd /var/www/html/

// を記述
sudo touch phpinfo.php

http://192.168.33.10/phpinfo.phpでアクセスを確認。
しかし、検索するとwindows環境の設定ばかりがひっかかる。開発Linux用のまとめを自分で作成しないと時間の無駄でダメだこりゃ。

プロジェクトをapacheのドキュメントルートに移動

面倒臭くなってきたw。準備が長いって。railsは偉大だ。。。


// プロジェクトフォルダをDocument rootに移動
sudo mv test/ /var/www/html/

http://192.168.33.10/test/test/でアクセス。なんかWarning: strtotime()とかでてる。
php.iniをいじってdate.timezone = Asia/Tokyoにすればいいらしい。


php --ini
Configuration File (php.ini) Path: /etc
Loaded Configuration File:         /etc/php.ini
Scan for additional .ini files in: /etc/php.d

sudo vi /etc/php.ini

// 変更
date.timezone = Asia/Tokyo

// 再起動
sudo service httpd restart

うまく表示された。でも、まだDebugKit is not installed.とかいう警告がある。プラグインが必要らしい。というわけでさらに続きます。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年11月16日日曜日

Ruby メタプログラミング 基礎トレーニング

  1. Stringオブジェクト”hello”に"world"を追加するworldメソッドをオープンクラスを用いて実装せよ。
  2. Stringオブジェクト”hello”に"world"を追加するworldメソッドを特異メソッドを用いて実装せよ
  3. Stringオブジェクト”hello”のclassを答えよ
  4. Stringクラスのclassを答えよ

1.回答


class String
  def world
    self << "world"
  end
end

puts "hello".world

参考メタプログラミングrubyP38

2.回答


str = "hello"

def str.world
  self << "world"
end

puts str.world

参考メタプログラミングrubyP150

3.回答


puts "hello".class # String

参考メタプログラミングrubyP47

4.回答


puts String.class # Class

参考メタプログラミングrubyP47

この記事がお役にたちましたらシェアをお願いします

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

2014年11月15日土曜日

ruby使いが手を動かして学ぶphp

久しぶりにphpを使わなければいけなくなりました。といっても少しですけど。
普段はjava, objective-c, swift, ruby使いなのですが、phpでCI環境を整えることになったので色々と学習&メモ。

ちなみにぼくが最後にphpで開発したのはverson4の最後の時です。php好きじゃないんだけど、まあ、仕方ないっすねー(棒)。rubyは素晴らしい言語だけど、使える人が本当に少ないのが弱点。結局両方用意しないといけなくなってしまう。(愚痴なので気にしないでくださいw)

せっかくなのでrubyの視点からみたphpの感想もちょこちょこ記載していきたいと思います(怒らないでねw)

環境

  • mac Yosemite
  • vagrant1.6.5

開発はvagrantのcentos6.5で行います。macに入っているphpはext-mcryptとかいうライブラリがなくてシンボリックリンクはったり面倒なんで。

php5.5のinstall

まずはchefを使わないで普通にinstallして基礎から学習します。php久しぶりなんで。
(chefを使った管理は多分あとで書きます。どうせやる必要あるんで)


 // yumを最新にupdate
 sudo yum -y update

 // epelのリポジトリを追加
 sudo rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

 // remiのリポジトリを追加
 sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

 // php install
 sudo yum install --enablerepo=remi --enablerepo=remi-php55 php php-opcache php-devel php-mbstring php-mcrypt php-mysqlnd php-phpunit-PHPUnit php-pecl-xdebug

 php -v
 PHP 5.5.19 (cli) (built: Nov 13 2014 07:52:05) 

install完了。次。

Composerをinstall

Composer

パッケージ管理ツールです。rubyでいうbundler、javaでいうgradle, movenのようなものです。

install

まずはchefを使わないで普通にinstall。勉強勉強。


curl -sS https://getcomposer.org/installer | php
#!/usr/bin/env php
All settings correct for using Composer
Downloading...

Composer successfully installed to: /Users/{username}/Documents/study/php/test/composer.phar
Use it: php composer.phar

OK、次。php composer.phar install


php composer.phar install
Composer could not find a composer.json file in /Users/{username}/Documents/study/php/test
To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section

composer.jsonというファイルが必要らしい。アプリのフレームワークにはrailsをインスパイアしたフレームワークcakephpを使います。

{
    "name": "example-app",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ],
    "require": {
        "cakephp/cakephp": ">=2.4.9"
    },
    "config": {
        "vendor-dir": "Vendor/"
    }
}

cakeのサイトの説明をそのまま使います。もう一度実行。


php composer.phar install
Loading composer repositories with package information
Initializing PEAR repository http://pear.cakephp.org
Installing dependencies (including require-dev)
  - Installing cakephp/cakephp (2.5.6)
    Downloading: 100%         

Writing lock file
Generating autoload files

Vendor以下に色々ある。まだよくわからないけど次。

プロジェクト作成

bakeコマンドでプロジェクトを作成。railsコマンドと同じかな。なんでcakeじゃないんだろうか。


Vendor/bin/cake bake project test

// something

warningでてるけどProject baked successfully!だからOKなのかな?無視して次。

testフォルダを発見。中に色々フォルダがある。うまくいったっぽい。でも、設定を変える必要があるらしい。

CAKE_CORE_INCLUDE_PATH を相対パスに変更


define(
    'CAKE_CORE_INCLUDE_PATH',
    ROOT . '/Vendor/cakephp/cakephp/lib'
);

準備完了。DBはmysqlを使います。

Mysql5.6をinstall


rpm -qa | grep mysql

mysql-libs-5.1.73-3.el6_5.x86_64
php-mysqlnd-5.5.19-1.el6.remi.x86_64

// delete mysql5.1
sudo yum remove mysql-libs-5.1.73-3.el6_5.x86_64

// add rpm
yum install http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm

// install mysql
yum install mysql mysql-devel mysql-server mysql-utilities

mysql --version
mysql  Ver 14.14 Distrib 5.6.21, for Linux (x86_64) using  EditLine wrapper

OK。長すぎるので分割して続きます。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年11月12日水曜日

android sqlite3 unit test

androidでsqlite3のunit testを実施する場合、通常のテストを実施すると既存のdbを上書きしてしまいます。
なので、 androidにはテスト用にRenamingDelegatingContextというクラスが用意されているので、このクラスでテストDBを作成してテストします。 こうすることにより、本番用のDBを操作しないでテストを行うことができます。

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="21"
  • andoid studio 0.8.14

サンプル


    public void testIsFaceboookAuthentication_true() {
        Context context = new RenamingDelegatingContext(mActivity.getApplicationContext(), "test_");
        // テストデータ登録
        TestUserHelper.registerTestUser(context);
        assertTrue(User.isFaceboookAuthentication(context));
        // テストデータ削除
        User.delete(mActivity.getApplicationContext(), null);
    }

Context context = new RenamingDelegatingContext(mActivity.getApplicationContext(), "test_")を使うことでテスト実行時に、プレフィックスに指定した「test_」が付加された「test_xxxxx.db」というファイル名としてDBが作成されます。こちらのDBを利用することで、各テストケース実行時に毎回クリーンなDBの状態で開始できます。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年11月9日日曜日

野村克也の「菜根譚」感想

本屋に立ち寄って思わず衝動買い。パラパラとめくっただけで良い本だなと思ったので。

菜根譚とは

菜根譚とは今から400年前に中国で書かれた本である。野村克也氏だけでなく、巨人をV9に導いた川上哲治氏も愛読していた本だという。

ITエンジニア(時代に)が読むべき本?

Google worksの次に手に取った本だが面白かった。時代が変わっても不変な考え方は多い。
例えば、googleのミッションステートメント「邪悪になるな(Don't be evil)」は、菜根譚の「心のこもった言葉が人の気を動かし、組織の基礎になる」を具現化している。
なぜなら、googleはミッションステートメントを守るために巨大な中国市場から利益を無視して撤退した。googleは中国400年前の金言をしっかり守ったわけだ。
撤退した市場が中国市場とは皮肉な話であるが。

読者ターゲット

主に経営者とか管理者だと思う。でも、組織に属していないフリーのエンジニアが読んでも得られることは多いと思います。

心に残った言葉

少し紹介します

早熟は晩成にはかなわない。

これは確かにそうですね。年齢を重ねて学べば学ぶほどに己の無知が理解できるし、心も頭も体も鍛錬しなければいけないことに気づきますね。勢いだけでなく、理論で物事をコントロールできるようになると調子の波が減ります。

小さなことにも手を抜かない。

当たり前のことだけど、なかなかできないことです。一流であればあるほど基礎を重視し、小さなことにも手を抜かない傾向がありますね。

一つのことをまっとうすることで得られるものは多い。

本当にその通りです。何をやってもうまくいく人は、たいてい本質を見抜くことができる人です。
生まれつき本質を見抜く能力が高い人もいますが、たいていの人は後天的に身につけていきます。その一番確実な方法は得意を極めることですね。

素質は平凡でも非凡な努力で道を極める。

これは島田紳助さんも言っていた気がしますね。才能と努力をかけたものがその人の総合的な能力だと。
才能が平凡な3でも努力が5できればなんとかなると。さすがに才能が1だと無理みたいですがw

まとめ

400年前にこんな優れた本が書かれていたとは素晴らしいですね。人間の寿命なんてたかだか100年程度なので、やっぱ道を極める人の思考は似るんですね。

この記事がお役にたちましたらシェアをお願いします

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

Swift optional

  • xcode6.1

optionalの理解が曖昧なのでまとめました。

Forced unwrapping

optional型を強制的に非optional型にする。変数に!を付加する。


class Goku {
    func talk() -> String {
        return "ossu"
    }
}

var goku :Goku? = Goku()

println(goku!.talk()) # "ossu"

!を付加しないとコンパイルエラーが発生する。

Optional Chaining

optional valueのあとに?をつけて呼び出す。optional chainingを呼び出した結果は常にoptional valueである。


class Goku {
    func talk() -> String {
        return "ossu"
    }
}

var goku :Goku? = Goku()

println(goku?.talk()) # Optional("ossu")

Implicitly Unwrapped Optional

変数をImplicitly Unwrapped Optional型で宣言する際には、?ではなく、!を使う


class Saiyajin {
    
    var version: Int {
        return 1
    }
}

var saiyajin: Saiyajin! = Saiyajin()
saiyajin.version # 1

ついでにForced unwrappingとOptional Chainingの場合


var saiyajin2: Saiyajin? = Saiyajin()
saiyajin2?.version # Some[1]
saiyajin2!.version # 1 

出力する結果は同じです。

nilの使い方



class Saiyajin {
    
    var version: Int {
        return 1
    }
}

var saiyajin: Saiyajin! = nil
saiyajin.version # ランタイムエラー

scalaを組んだことないと慣れるまでちょっと大変かもしれませんね。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年11月8日土曜日

mac Yosemiteでandroid studioを動かす

mac Yosemiteでandroid studioを動かすと「java se6のランタイムをインストールする必要があります」というエラーメッセージが表示されるのでその対策方法です。

  • mac
  • android studio mac 0.8.14

まずはmacのjavaのバージョンを確認。


java -version
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

普通のjavaです。ただ残念なことに、IntelliJ IDEAをはじめとするJetBrainsのIDEはデフォルトでAppleのJava6を必要とします。
そうしないと、フォントのサブピクセルレンダリングが働かないためフォントが幾分汚く見えたり、エディタ領域のピンチイン・アウトでのズームイン・アウトも効かなくなるそうです。公式サイト説明
なので、Java for OS X 2014-001を導入します。

Java for OS X 2014-001

上記のjavaをinstallすればandroid studioは正常に動作するようになります。
また、IntelliJ IDEAをJava 6で動作させてもJava7、Java8、またJava9アプリケーション開発には問題ないそうです。

以上

この記事がお役にたちましたらシェアをお願いします

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

chef ディレクトリ作成

chefでのディレクトリ作成方法(mkdir)。webアプリを配置するときなどに良く利用するのでメモ。

環境

  • box centos6.5 on vagrant 1.6.5
  • chefDK 0.3.0

# web app root create
directory "/var/www/rails/public/app_web" do
  owner "root"
  group "root"
  recursive true
  mode 0755
  action :create
  not_if {File.exists?("/var/www/rails/public/app_web")}
end
  

not_ifがないと、再実行時にもう一度作成しようとするので注意。recursiveはそのまま再帰を意味します。

以上

参考

この記事がお役にたちましたらシェアをお願いします

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

2014年11月1日土曜日

ruby moduleのまとめ その1

今更ながらrubyのmoduleのまとめ。
railsでのアプリ開発作成数も10を超えたので、もう一度足元から固めるための再学習メモ。
まさかこんなにrubyを長く使うようになるとは思わなかったなあ。chefを含めると、javaより利用してる時間長いです。

  • mac
  • ruby 2.0.0

Moduleとは

  • クラスと同じくメソッドを定義できる。
  • オブジェクトは作成できない
  • クラスメソッドが利用できない

Basic sample. Good Method.

 

    # coding: utf-8
    module Test

      def test
        puts 'test'
      end

      def self.test1
        puts 'test1'
      end

    end

    class ModukeTest
      include Test
    end

    modukeTest = ModukeTest.new
    modukeTest.test

    # test

    

Basic sample. Module instance is not able to create.

 

    test = Test.new
    test.test

    undefined method `new' for Test:Module (NoMethodError)

    

Basic sample. Module class method is not able to call.

 

    modukeTest = ModukeTest.new
    modukeTest.test1

    undefined method `test1' for # (NoMethodError)

    

使い方

  • include
  • extend

includeは上記の通り。extendはクラスメソッドしてモジュールを使えるようになる。

 

    # coding: utf-8
    module Test2

      def test2
        puts 'test2'
      end

    end

    class ModukeTest
      extend Test2
    end

    ModukeTest.test2

    # test2

    

色々なオープンソースを読んでいる人はわかると思いますが、extendはほとんど使われていない。80%くらいはinclude。よくある実装パターンは以下のような感じ。

 

    # coding: utf-8
    module Test

      def test
        puts 'test'
      end

      class Taro
        def introduce
          puts "may name is taro"
        end
      end

    end

    class ModukeTest
      include Test
    end

    taro = ModukeTest::Taro.new
    taro.introduce

    # may name is taro

    

railsなんかはこういったパターンでよく記述されていますね。

参考

この記事がお役にたちましたらシェアをお願いします

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

2014年10月29日水曜日

Android SDK Content Loader 0%

ecipseで開発している人なら一度は出たことがあるバグではないでしょうか。発生するたびに検索するのが面倒なので解決方法を記載。海外サイトでもよく引っかかるのでついでに英語も。

環境

  • windows7

日本語

eclipseのバグで発生するAndroid SDK Content Loader 0%。cleanとかやっても復活しないときは以下の方法をやってみること

  1. ./androidフォルダへ
  2. cacheフォルダ削除
  3. ddms.cfgファイルを削除
  4. eclipse再起動
  5. 復活

English

if your eclipse occured Android SDK Content Loader 0%, you should action next step.

  1. move to ./android folder
  2. delete cache folder
  3. delete ddms.cfg
  4. reboot eclipse
  5. you feel happy!

android studioの正式リリース版はまだかなあ...

以上

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

knife solo Net::SSH::AuthenticationFailed on AWS LINUX

環境

  • aws linux
  • chefDK 0.3.0

awsでデフォルトユーザー(ec2-user)のままknife soloを実行したらNet::SSH::AuthenticationFailedエラーが発生。
原因はawsでデフォルト作成された秘密鍵のパスワードがわからないから。
インスタンス作成時に自動生成される秘密鍵はパスワードなしで作成されているわけではないってことかな?

解決するにはknife cookをするときに秘密鍵を指定してやればよい。


  knife solo prepare -i ~最初に貰った秘密鍵 ec2-user@vvvvvvvvvvvvxxxxxxxxx.com
  

上記のようにするといつも通りchefが動作する。

以上

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月28日火曜日

[Berkshelf::APIClient::TimeoutError] Unable to connect to: http://api.berkshelf.com/

vagrantでcentos6.5の環境を構築し、chefDKをinstallしてopscodeからソースを取得しようとしたら題名のエラーが発生した。

環境

  • box centos6.5 on vagrant 1.6.5
  • windows7 & mac Yosemite
  • chefDK 0.3.0

原因はconnection.rbファイルのopen_timeoutの設定時間が短すぎること。どうやらwindowsやmac上のvagrantで実行していると名前解決に時間がかかってしまうらしい。

対処方法は、connection.rbを書き換えるしかないようです。ということでLet's hacking。


  find /. -name connection.rb

  // result...
  /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/berkshelf-api-client-1.2.0/lib/berkshelf/api_client/connection.rb
  

発見。このconnection.rbを書き換える。

before


  def initialize(url, options = {})
    options         = options.reverse_merge(retries: 3, retry_interval: 0.5,
      open_timeout: 3, timeout: 30)

after


  def initialize(url, options = {})
    options         = options.reverse_merge(retries: 3, retry_interval: 0.5,
      open_timeout: 30, timeout: 30)

保存したらいつものようにberks installをすれば上手くいくはずです。

以上

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月26日日曜日

Swift アプリ作成 基本その1 クラスの作成とUnit test

a swift tourを駆け足で終わらせたので、いよいよSwiftでアプリを作成していきます。
詳しく説明していければいいのですが、swiftで本格的に開発するまで1ヶ月くらいしか勉強期間がないのでそこそこ飛ばしていきます。(他もやること色々あるのです...)

環境

  • xcode6.0.1

最初に作成するアプリはチャットアプリ。Listにコメントをならべる「アレ」です。

プロジェクト作成

object-c時代と変わらないです。
xcode → File → New → Projectでプロジェクト作成。言語だけswiftにします。また今回はmaster detail applicationを利用します。
コードの中を見てみると、objective-cの時とあまり変わってない...。本当に開発効率あがるのでしょうか。

Userオブジェクトの作成

User情報を格納するクラスを作成します。以下の通りです。


import Foundation

class User {
    let id: Int
    var name: String?
    var description: String?

    init(id: Int) {
        self.id = id
    }
    
    convenience init(id: Int, name: String, description: String) {
        self.init(id: id)
        self.name = name
        self.description = description
    }
    
    func displayName() -> String {
        if let name = self.name {
            return name
        }
        return "no name"
    }
    
    func displayDescription() -> String {
        if let description = self.description {
            return description
        }
        return "no description"
    }

}

convenience initは委譲イニシャライザ。要はself.initを中で呼ないといけないってことです。呼ばないとエラーになります。
displayName(),displayDescription()はoptinalのString?をString型で返しています。Optional Bindingで非Optional型に変換(String型ってこと)しています。

Userオブジェクトのテストコードの作成

XCTestCaseを継承して作成します。Quickなるものが熱いらしいが、時間ないしXCTestCaseで十分。


import UIKit
import XCTest

class UserTest: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testDisplayName() {
        var user = User(id: 1)
        XCTAssertEqual(user.displayName(), "no name")
    }
    
    func testDisplayDescription() {
        var user = User(id: 1)
        XCTAssertEqual(user.displayDescription(), "no description")
    }
    
    func testDisplayName_name() {
        var user = User(id: 1, name: "taro", description: "hello")
        XCTAssertEqual(user.displayName(), "taro")
    }
    
    func testDisplayName_description() {
        var user = User(id: 1, name: "taro", description: "hello")
        XCTAssertEqual(user.displayDescription(), "hello")
    }
    
    
}


produce → test で実行。全部うまくいってグリーンになると思います。
しかし相変わらずTestコードの説明をしているサイトが少なくてうんざりしたのは内緒w(テストコードを書くって、ちょっとした習慣なんですけどね。なのにほとんどみんなやらない)

さて、この調子で作成していきます。次はchatクラスを作成します。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

Swift 学習チートシート3

Swift 学習メモ3

  • xcode6.0.1

基本知識のまとめ


1.enum
1.1.scalaとほぼ同じ

* apple公式に記載されているself.rawValueはなくなったっぽい

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Fice, Six, Seven, Eight, NIne, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "queen"
        default:
            return "ng"
        }
    }
}

let ace = Rank.Ace
ace.toRaw()
ace.simpleDescription()
let two = Rank.Two
two.toRaw()
let jack = Rank.Jack
jack.toRaw()

返り値がintなので、値は順番に割り当てられるようだ。微妙な仕様だ。

1.2.値の自動割当なし

enum Suit {
    case Spades, Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "Spades"
        case .Hearts:
            return "Hearts"
        default:
            return "ng"
        }
    }
}

let hearts = Suit.Hearts
hearts.simpleDescription()

こっちのほうがしっくりくるし、よく使いそう。

2.struct
classと基本同じ。あんま使わない。

struct Card {
    var rank : Rank
    var suit : Suit
    func simpleDescription() -> String {
        return "rank \(rank.simpleDescription()) , suit\(suit.simpleDescription())"
    }
}

let card = Card(rank: .Three, suit: .Spades)
card.simpleDescription()

class Card2 {
    var rank : Rank
    var suit : Suit
    init(rank: Rank, suit: Suit) {
        self.rank = rank
        self.suit = suit
    }
    
    func simpleDescription() -> String {
        return "rank \(rank.simpleDescription()) , suit\(suit.simpleDescription())"
    }
}

let card2 = Card2(rank: .Three, suit: .Spades)
card.simpleDescription()

クラスの場合はinitがないとコンパイルできない

2.Protocol
クラスやstructを拡張する
objective-cと一緒。rubyのmixinのほうが好み。

protocol ExampleProtocol {
    var simpleDescription: String {get}
    mutating func adjust()
}

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "a vert simple class"
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "adjust"
    }
    
}

var a = SimpleClass()
a.adjust()

mutatingは名前付け。javaのabstructみたいなもんかな。

3.extension
型の拡張
rubyのオープンクラスと同じ。これはrubyより洗練されている

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}

10.simpleDescription

4.Generics
まんまjava。このサイトではandroidでいつも紹介しているので割愛。

以上でA Swift Tourは終了。複数言語のプログラムを習得していればこのA Swift Tourを終了させればそこそこswiftでプログラムがかけると思います。
appleのLanguage Guideをみるとsubscriptとかclosureについて記載してあるので、使いたいと思う機能を写経しましょう。

というわけで次からは本格的にサンプルアプリの作成をしていきたいと思います。でわ。

ちなみにA Swift Tourはブログ用に写経しながらで5hくらいでした。参考までに。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月25日土曜日

2015年問題から見える日本のIT業界の問題点 その4

人を増やしても問題は解決しない

さて、2015年問題に話を戻そう。
この2015年問題、これは人を増やしても解決しないと思う。どれだけ人を増員しても、結局はヘンテコなシステムが構築され、その保守や改修がビジネスとして続いていくことに異論を挟む人はいないだろう。

そもそも、人を増やせば問題が解決するという考えは、テクノロジーの世界ではあまり良い考えとはいえない。産業時代の考え方だ。
もちろん最低限の人数は必要だが、根本的な問題の解決にはならない。
僕の経験上、問題を解決する優れた方法は「問題を考える時間をもつ」「少数の優秀な人でグループを作る」ということである。

「少数の優秀な人でグループを作る」の効果は、googleやfacebookを見れば明らかだろう。少数で組織やグループを構成するのは情報の共有がしやすいからだ。とはいえ、優秀な人物を集めるには時間がかかる。となると、「問題を考える時間をもつ」ことが重要となる。

問題点は日々、状況によって変化する。今解決できない問題でも、時間が経ち知識が増えると解決することはよくあることだ。例えばある製品を作成することになり、7割の出来で先に進むというのは、製品を世に早く出すという点だけでなく、知識を蓄積するという点でもすぐれた仕事の進め方だ。ただ、このやり方の問題点は、周りがそういった製品を認めてくれる環境にあるかどうかが大きい。僕の経験では、スピード重視のベンチャー企業以外ではあまり良い顔をされない。結局、あれもこれも欲しがるのだ。足し算の仕事をできる人は多いが、引き算の仕事が出来る人は本当に少ない。

「問題を考える時間をもつのが重要」とはいえ、日々仕事に追われるとタスクを消化するだけで精一杯になってしまうのは仕方がないことだ。 redmineのチケットと重要タグのついたメールを見ながら焦る気持ちを抱く感覚が僕にはよくわかる。効率化したタスク管理はストレスの効率化と数値化はしてくれない。責任感の強い人やまじめな人が鬱になるのは、こういった効率化したタスク管理にも原因があるように思う。

また、日々の作業を事務的にタスクをこなすだけではいけない。何度もいうが、タスクが増えたから人を増やそうというのは愚か者の発想だ。
もちろん人を入れることで解決することもある。だが、それは、たまたまその問題が人が足りないのが原因だったからに過ぎない。たいていの原因は異なるところにある。その原因をさぐって解決していく習慣をつけると、仕事は楽になるし、成長出来る。

原因を考え、検証、実行、分析ができる習慣を身につけよう。

この記事がお役にたちましたらシェアをお願いします

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

Android CI設定 jenkins and Bitbucket and Deploygate

新規AndroidアプリのjenkinsへのCI設定をいつも間違えるので、複数回やらかしたエラーの忘備録。

jenkinsでおこなっていること

  • jenkinsでgitからcloneしてきたソースコードをgradleでビルドしてapkを出力する
  • ビルドして出力されたapkをDeploygateに飛ばす

環境

  • Jenkis
  • Bitbakets or gitHub
  • android studio開発アプリ(gradle build)

よく忘れる環境設定チェックリスト

1.ビルドサーバーのandroidを最新にする

SDKを開発環境と同じように最新にしておかないと失敗するので、開発環境のadkをupdateしたときは注意が必要

2,local.propertiesの配置を忘れる

andoirdアプリの開発では、gitでlocal.propertiesは管理しないのが基本。
なので、最初のgit cloneでソースを持ってきたあと、local.propertiesを設定する必要がある。


cd {project_root}

vi local.properties

sdk.dir=/usr/local/android-sdk-linux

// 権限はjenkins
chown jenkins:jenkins local.properties

3.unable to unlink old 'volley/build/generated/source/buildConfig/debug/com/android/volley/BuildConfig.java' (Permission denied)

上記のエラーはgitの管理ファイルに余計なものを含ませていたため発生。上記の場合は、volleyのbuildファイル以下をgitで管理していたのが原因


volleyのbuild以下を削除しgitにcommit&push。
.gitignoreでvolley/buildも追加する必要がある。

教訓

CI環境も常に改善が必要ということです。

この記事がお役にたちましたらシェアをお願いします

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

androidアプリを5.0(Material Design)に対応する その3 Google アナリティクス SDK v4の導入

前回ImageViewでユーザーアイコンの表示を円形に表示させました。
今回はグロースハックのお友達Google アナリティクスです。これも5.0関係ないけど、今丁度僕が実装中なのでついでに記載。

SDK v4からはlibは不要となってgoogle play serviceを導入するだけで動きます。んー、便利!
色々と詰め込みすぎで個人的に不安ですが。。。

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="21"
  • andoid studio 0.8.9

アナリティクスを管理するAnalyticsManagerクラスを作成します。googleのコードを参考にして作成しました。


import android.content.Context;

import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;
import android.util.Log;


public class AnalyticsManager {

    private static Context sAppContext = null;

    private static Tracker mTracker;
    private final static String TAG = "AnalyticsManager";

    public static synchronized void setTracker(Tracker tracker) {
        mTracker = tracker;
    }

    private static boolean canSend() {
        // We can only send Google Analytics when ALL the following conditions are true:
        //    1. This module has been initialized.
        //    2. The user has accepted the ToS.
        //    3. Analytics is enabled in Settings.
        return sAppContext != null && mTracker != null;
    }

    public static void sendScreenView(String screenName) {
        if (canSend()) {
            mTracker.setScreenName(screenName);
            mTracker.send(new HitBuilders.AppViewBuilder().build());
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Screen View recorded: " + screenName);
            }
        } else {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Screen View NOT recorded (analytics disabled or not ready).");
            }
        }
    }

    public static void sendEvent(String category, String action, String label, long value) {
        if (canSend()) {
            mTracker.send(new HitBuilders.EventBuilder()
                    .setCategory(category)
                    .setAction(action)
                    .setLabel(label)
                    .setValue(value)
                    .build());

            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Event recorded:");
                Log.d(TAG, "\tCategory: " + category);
                Log.d(TAG, "\tAction: " + action);
                Log.d(TAG, "\tLabel: " + label);
                Log.d(TAG, "\tValue: " + value);
            }

        } else {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Analytics event ignored (analytics disabled or not ready).");
            }
        }
    }

    public static void sendEvent(String category, String action, String label) {
        sendEvent(category, action, label, 0);
    }

    public Tracker getTracker() {
        return mTracker;
    }

    public static synchronized void initializeAnalyticsTracker(Context context) {
        sAppContext = context;
        if (mTracker == null) {
            int useProfile;
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Analytics manager using DEBUG ANALYTICS PROFILE.");
                useProfile = R.xml.analytics_debug;
            } else {
                useProfile = R.xml.analytics_release;
            }
            mTracker = GoogleAnalytics.getInstance(context).newTracker(useProfile);
        }
    }

}

あとはコード中で必要な箇所で呼び出すだけです

画面名


        if (!BuildConfig.DEBUG) {
            /* [ANALYTICS:SCREEN]
             * TRIGGER:   View the Login screen.
             * LABEL:     'Login'
             * [/ANALYTICS]
             */
            AnalyticsManager.sendScreenView(SCREEN_LABEL);
        }

activityやFragmentのonCreateで呼びます。

イベント


   
            if (!BuildConfig.DEBUG) {
                /* [ANALYTICS:EVENT]
                 * TRIGGER:   button is pushed.
                 * CATEGORY:  'Show List Detail'
                 * ACTION:    buttonListDetail
                 * LABEL:     visible gone
                 * [/ANALYTICS]
                 * */
                AnalyticsManager.sendEvent("Show List Detail", "buttonListDetail", label, 0L);
            }

button押下時などのアクション時に呼びます。

公式サイトにはApplicationクラスに記述するのがお勧めと記載されていますが、実際はutilとしてAnalyticsManagerクラスをつくっているのがgoogleらしいですねw。 でもこちらの方が使いやすいし、汎用性もあります。debugとreleaseのxmlファイルでの分け方はマネすると便利です。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

android studio androidアプリのデバッグapkとリリースapkを出力する

  • 公開日:2014年10月25日
  • 最終更新日:2016年03月17日

記事概要

android studioでアプリを作成した場合の、gradleを使ったビルドの方法を記載した記事です。

android6.0対応版の記事を記述しました。

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="21"
  • andoid studio 0.8.9
  • windows7

はじめに

android studioでアプリを作成したので、gradleを使ったビルドのメモを残しておきます。
コマンドを叩いてapkを出力できるようにしておくと本当に便利なので、設定することをオススメします。

build.gradle

debugとrelease用のkeystoreファイルが作成してあること前提で話をすすめます。

signingConfigsの設定

signingConfigsはアプリファイル(.apk)を作成するときに必要となるkeystoreの設定します。
debug用や、release用のkeystoreを用意する必要があります。(日本語のコメントはNGらしいなので、下記のままだと利用できません。)

build.gradle

android {
    // something
    signingConfigs {
        debug {
            // debug.keystoreファイルのある場所を設定
            storeFile file("./debug.keystore")
            storePassword "android"
            keyAlias "androiddebugkey"
            keyPassword "android"
        }
        release {
            // release.keystoreファイルのある場所を設定
            storeFile file("./release.keystore")
            storePassword "releaseandroid"
            keyAlias "releaseandroid"
            keyPassword "releaseandroid"
        }
    }
}

続けてbuildTypesを設定します。

build.gradle

android {
    // something
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            signingConfig signingConfigs.release
            //runProguard false
            //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

上記はproguardを指定していませんが、実際は指定したほうが良いです。signingConfigは必須です。

build.gradle

android {
    // something
    lintOptions {
        abortOnError false
    }
}

総てのlintに対応するのは無理があるので、設定することになると思います。

コマンドラインからビルド

プロジェクトのgradlew.batファイルがあるフォルダまで移動します。

terminal

gradlew.bat build

windowsなのでgradlew.batを使います。
macやlinuxの場合はgradlewにします。
成功すると、build/outputs/フォルダの配下にapkが出力されているはずです。

以上です

PICK UP

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月23日木曜日

androidアプリを5.0(Material Design)に対応する その2 画像を円形にする

前回android5.0のライブラリを導入したので、画面も5.0らしくモダンな画面に変更していきます。
今回はありがちなImageViewでのユーザーアイコンの表示を円形に表示させます。

変更前

変更後

この機能はandroid5.0でなくても導入可能なので、ぜひ導入してみてください。

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="21"
  • andoid studio 0.8.9

ImageViewを継承したBezelImageViewというコードを作成します。googleのコードを参考にしてます。


import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
 * An {@link android.widget.ImageView} that draws its contents inside a mask and draws a border
 * drawable on top. This is useful for applying a beveled look to image contents, but is also
 * flexible enough for use with other desired aesthetics.
 */
public class BezelImageView extends ImageView {
    private Paint mBlackPaint;
    private Paint mMaskedPaint;

    private Rect mBounds;
    private RectF mBoundsF;

    private Drawable mBorderDrawable;
    private Drawable mMaskDrawable;

    private ColorMatrixColorFilter mDesaturateColorFilter;
    private boolean mDesaturateOnPress = false;

    private boolean mCacheValid = false;
    private Bitmap mCacheBitmap;
    private int mCachedWidth;
    private int mCachedHeight;

    public BezelImageView(Context context) {
        this(context, null);
    }

    public BezelImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BezelImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        // Attribute initialization
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BezelImageView,
                defStyle, 0);

        mMaskDrawable = a.getDrawable(R.styleable.BezelImageView_maskDrawable);
        if (mMaskDrawable != null) {
            mMaskDrawable.setCallback(this);
        }

        mBorderDrawable = a.getDrawable(R.styleable.BezelImageView_borderDrawable);
        if (mBorderDrawable != null) {
            mBorderDrawable.setCallback(this);
        }

        mDesaturateOnPress = a.getBoolean(R.styleable.BezelImageView_desaturateOnPress,
                mDesaturateOnPress);

        a.recycle();

        // Other initialization
        mBlackPaint = new Paint();
        mBlackPaint.setColor(0xff000000);

        mMaskedPaint = new Paint();
        mMaskedPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

        // Always want a cache allocated.
        mCacheBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);

        if (mDesaturateOnPress) {
            // Create a desaturate color filter for pressed state.
            ColorMatrix cm = new ColorMatrix();
            cm.setSaturation(0);
            mDesaturateColorFilter = new ColorMatrixColorFilter(cm);
        }
    }

    @Override
    protected boolean setFrame(int l, int t, int r, int b) {
        final boolean changed = super.setFrame(l, t, r, b);
        mBounds = new Rect(0, 0, r - l, b - t);
        mBoundsF = new RectF(mBounds);

        if (mBorderDrawable != null) {
            mBorderDrawable.setBounds(mBounds);
        }
        if (mMaskDrawable != null) {
            mMaskDrawable.setBounds(mBounds);
        }

        if (changed) {
            mCacheValid = false;
        }

        return changed;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mBounds == null) {
            return;
        }

        int width = mBounds.width();
        int height = mBounds.height();

        if (width == 0 || height == 0) {
            return;
        }

        if (!mCacheValid || width != mCachedWidth || height != mCachedHeight) {
            // Need to redraw the cache
            if (width == mCachedWidth && height == mCachedHeight) {
                // Have a correct-sized bitmap cache already allocated. Just erase it.
                mCacheBitmap.eraseColor(0);
            } else {
                // Allocate a new bitmap with the correct dimensions.
                mCacheBitmap.recycle();
                //noinspection AndroidLintDrawAllocation
                mCacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                mCachedWidth = width;
                mCachedHeight = height;
            }

            Canvas cacheCanvas = new Canvas(mCacheBitmap);
            if (mMaskDrawable != null) {
                int sc = cacheCanvas.save();
                mMaskDrawable.draw(cacheCanvas);
                mMaskedPaint.setColorFilter((mDesaturateOnPress && isPressed())
                        ? mDesaturateColorFilter : null);
                cacheCanvas.saveLayer(mBoundsF, mMaskedPaint,
                        Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG);
                super.onDraw(cacheCanvas);
                cacheCanvas.restoreToCount(sc);
            } else if (mDesaturateOnPress && isPressed()) {
                int sc = cacheCanvas.save();
                cacheCanvas.drawRect(0, 0, mCachedWidth, mCachedHeight, mBlackPaint);
                mMaskedPaint.setColorFilter(mDesaturateColorFilter);
                cacheCanvas.saveLayer(mBoundsF, mMaskedPaint,
                        Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG);
                super.onDraw(cacheCanvas);
                cacheCanvas.restoreToCount(sc);
            } else {
                super.onDraw(cacheCanvas);
            }

            if (mBorderDrawable != null) {
                mBorderDrawable.draw(cacheCanvas);
            }
        }

        // Draw from cache
        canvas.drawBitmap(mCacheBitmap, mBounds.left, mBounds.top, null);
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        if (mBorderDrawable != null && mBorderDrawable.isStateful()) {
            mBorderDrawable.setState(getDrawableState());
        }
        if (mMaskDrawable != null && mMaskDrawable.isStateful()) {
            mMaskDrawable.setState(getDrawableState());
        }
        if (isDuplicateParentStateEnabled()) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public void invalidateDrawable(Drawable who) {
        if (who == mBorderDrawable || who == mMaskDrawable) {
            invalidate();
        } else {
            super.invalidateDrawable(who);
        }
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return who == mBorderDrawable || who == mMaskDrawable || super.verifyDrawable(who);
    }
}

attrs.xml


<resources>

    <declare-styleable name="BezelImageView">
        <attr name="maskDrawable" format="reference" />
        <attr name="borderDrawable" format="reference" />
        <attr name="desaturateOnPress" format="boolean" />
    </declare-styleable>

</resources>

dimens.xml


   
    <dimen name="expert_icon_size">60dp</dimen>

circle_mask.xml


<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#000" />
</shape>

上記で完成。あとはlayoutのxmlに記載するだけです。


    <com.tset.testapp.ui.widget.BezelImageView
        android:id="@+id/image"
        app:maskDrawable="@drawable/circle_mask"
        android:layout_width="@dimen/expert_icon_size"
        android:layout_height="@dimen/expert_icon_size"
        android:scaleType="centerCrop"
        android:layout_marginRight="10dp" />

あとはコンパイルして表示すれば上記の画像のように円形の画像が表示されます。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

androidアプリを5.0に対応する その1 ライブラリの導入

さてさて、android5.0が正式にリリースされました。そして、今回のandroid5.0では、かなりの大幅な変更が行われました。
というわけでさっそく対応しようと思います。対応するアプリは現在4.4.2で作成中のアプリなので、参考になれば幸いです。
せっかくswiftに注力できると思ったのに…。

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="19"
  • andoid studio 0.8.9

gradleを以下のように書き換えます。

Before


apply plugin: 'com.android.application'

android {
    compileSdkVersion 19
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.test.testapp"
        minSdkVersion 14
        targetSdkVersion 19

        testApplicationId "com.test.testapp.test"
        testInstrumentationRunner "com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"
    }

    buildTypes {
        debug {

        }
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile project(':volley')
    compile 'com.android.support:support-v4:20.0.0'
    compile 'com.google.android.gms:play-services:5.0.89'
    compile 'com.google.code.gson:gson:2.3'
    compile files('libs/twitter4j-core-4.0.2.jar')
    androidTestCompile files('libs/espresso-1.1-bundled.jar')
}

After


apply plugin: 'com.android.application'

android {
    // 19 → 21
    compileSdkVersion 21
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.test.testapp"
        minSdkVersion 14
        // 19 → 21
        targetSdkVersion 21

        testApplicationId "com.test.testapp.test"
        testInstrumentationRunner "com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"
    }

    buildTypes {
        debug {

        }
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile project(':volley')
    // 20 → 21
    compile 'com.android.support:support-v4:21.0.0'
    // 6.1にする
    compile 'com.google.android.gms:play-services:6.1.+'
    compile 'com.google.code.gson:gson:2.3'
    compile files('libs/twitter4j-core-4.0.2.jar')
    androidTestCompile files('libs/espresso-1.1-bundled.jar')
    // これが欲しい
    compile 'com.android.support:appcompat-v7:21.0.+'
}


上記のように変更しないと、Attribute "theme" has already been definedやら、android:actionModeShareDrawableやらが発生します。
androidのupdateの弱点は、play-servicesがすぐに対応しなくなることですね。これは本当にやっかいです。

あとはいつものようにbuildをすればOK。テストコードを走らせて、動作確認がうまくいけば問題ないでしょう。

cardViewやRecycleViewあたりは、かなり良さそうなので積極的に利用していきたいですね。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月19日日曜日

Swift 学習チートシート2

Swift 学習メモ2

  • xcode6.0.1

基本知識のまとめ


1.関数
1.1.関数の中で関数を記述して呼び出す。

func returnFifteen() -> Int {
    var y = 10
    func  add() {
        y += 5
    }
    add()
    return y
}

returnFifteen()

1.2
関数を変数に格納して利用する
javaScriptでよく使うパターン

func makeIncrements() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}

var increment = makeIncrements()
increment(1)

* Point
(Int -> Int)
上記はIntで貰った関数をIntで返すという意味。


1.3.1.2を少し複雑にした関数
テストコードも各練習しないとダメだけど、とりあえず関数まで一気につぶす。

func hasAnyMathcers(list: [Int], condition: Int -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    
    return false
}

func lessThanTen(number: Int) -> Bool {
    return number < 10
}

var numbers = [20, 14, 17, 12]
hasAnyMathcers(numbers, lessThanTen)

*list: [Int], condition: Int -> Bool
配列とIntを引数にとってboolを返す関数を引数にとる

1.4.closure
クロージャー。
柔軟だけど、あんまやりすぎると可読性が落ちるので注意。テストコード必須。

var numbers = [20, 14, 17, 12]

let mappednumbers = numbers.map({number in 3 * number})

mappednumbers

ここまでの感想。
よく出来てる言語です。objective-cは極力使わない方向でiosの開発は進めていきたいですね。
あとはscalaでのプロジェクトの経験が凄い生きてるのが驚きです。scalaは流行らないと見切りをつけたんだけど、何の知識が役に立つかわからないもんですねえ。


2.class
javaとかrubyとかscalaのいいとこ取り。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A sharp with \(numberOfSides) sides."
    }
}

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

さらに続く

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

Swift 学習チートシート

次のiphoneアプリの開発からはSwiftでということが正式に決まったので学習開始。
ざっと見た感じscalaみたい。scalaはあんま得意じゃないです。playframeworkとか苦労した記憶しか無い。

  • xcode6.0.1

基本知識のまとめ


1.let 定数

let lable = "This width is"

2.var 変数

var test = "test"

3.文字列と数値を+でつなげても、自動で数値がstringには変換されない。
文字列の中で\()を使うことで数値を加えることが可能。

let apples = 3
let appleSummary = "I hava \(apples) apples."

# rubyの式展開にちょっと似ている。
# ruby
ape = 15
puts 'My ape is #{ape}"

4.配列

# 初期化
var shoppingList:[String] = []

型を宣言しないとエラーが発生する
× var shoppingList = []

5. optional
scalaのoptionとほとんど同じ

if文でtrueの場合

var optionalName: String? = "Jone appleseed"
var greeting = "hello"
if let name = optionalName {
    greeting = "HEllo, \(name)"
}

6. switch
型での判断が可能。
僕の中の言語の善し悪しの基準に、プログラム言語はswitchの機能で決まるという勝手な思い込みがあるのですが、ruby > scala > swiftという印象。
かなり高機能です。

var vegitable = "red peper"
switch vegitable
{
case "celery":
    let vagetableComment = "Everything is not good"
case "celery", "bule peper":
    let vagetableComment = "this is great!"
case let x where x.hasPrefix("red"):
    let vagetableComment = "common on"
default:
    let vagetableComment = "Everything is good"
}

whereが良い感じ。

7. func
関数
scalaのtupleみたいな機能が使える。いかにもモダンな言語という印象。

func culcStatisc(scores:[Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0
    
    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }
    
    return (min, max, sum)
    
}

let statistics = culcStatisc([100, 80, 90, 70])
statistics.min
statistics.sum

続く

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月13日月曜日

android Volleyを使ったエラーハンドリング rails grapeとの連携

またしてもVolleyネタです。今回は通常処理でなくエラーハンドリングの処理の場合を扱います。

環境

  • rails4.1.2 and grape
  • android:minSdkVersion="14"
  • android:targetSdkVersion="19"

ステータスコードを受け取る

volleyは、40Xや50Xのstatus codeを受け取ると、VolleyErrorを受け取るような仕様になっています。
サーバーAPIを使ったandroidの処理では、volleyとrails4.1.2 and grapeの組み合わせでjsonを使うのが一番早く作成できると思います(ただし、学習コストは高めかも)。


                //レスポンス失敗時のリスナー
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        executeVolleyError(error);
                    }
                }){

上記ではexecuteVolleyErrorというメソッドを呼び出しています。これはFragmentの共通処理クラス(親クラス)に記述しています。

executeVolleyErrorメソッド


    /**
     * Get Volley Error
     * @param error
     */
    protected void executeVolleyError(VolleyError error) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "error : " + error.getMessage());
            Log.d(TAG, "Status code : " +  error.networkResponse.statusCode);
        }
        if (error != null) {
            if (error.networkResponse == null) {
                Toast toast = Toast.makeText(getActivity().getApplicationContext(), getActivity().getApplicationContext().getResources().getString(R.string.network_error_404), Toast.LENGTH_LONG);
                toast.setGravity(Gravity.CENTER, 0, 0);
                toast.show();
                return;
            }

            // status code 400, status code 404
            if (HttpStatus.SC_BAD_REQUEST == error.networkResponse.statusCode || HttpStatus.SC_NOT_FOUND == error.networkResponse.statusCode) {
                Toast toast = Toast.makeText(getActivity().getApplicationContext(), getActivity().getApplicationContext().getResources().getString(R.string.network_error_404), Toast.LENGTH_LONG);
                toast.setGravity(Gravity.CENTER, 0, 0);
                toast.show();
            // status code 503
            } else if (HttpStatus.SC_SERVICE_UNAVAILABLE == error.networkResponse.statusCode) {
                Intent intent = new Intent(getActivity(), MaintenanceActivity.class);
                startActivity(intent);
                return;
            }
        }
    }

status codeが400(パラメーターエラー)と404(ページエラー)の場合は、エラーメッセージをToastで表示しています。
status codeが503の場合はメンテナンス画面に遷移させています。
当然ですが、各acticityやfragmentにゴリゴリとコピーするのはNGなので共通処理としてまとめましょう。

参考までにrails and grape(サーバーAPI)側のコードを以下に記載します。


  resource 'tests' do
    get '/' do
      return error!(Test.maintenanceJson, 503) if Test.isMaintenance?(params)
      return error!(Test.testParamError, 400) unless Test.checkParameter(params)
      tests = Test.tests(params)
      return Test.testJson(tests, params)
    end
  end

以上

この記事がお役にたちましたらシェアをお願いします

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

2014年10月7日火曜日

android Volleyを使ったListViewをリファクタリング

さてさて。久しぶりに会社絡み抜きでandroidアプリを作成しているのですが、もうひと踏ん張りで終了しそうです。

というわけで、今回はリファクタリングに焦点を当てます

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="19"

volleyを利用したListViewの画像取得をキャッシュする

volleyはここ半年くらいで利用し始めました。とっても便利なライブラリです。デフォルトでも十分な性能ですが、チューニングするとさらにすばらしいことに!

リファクタリング前
よくありがちなVolleyを使ったListView処理です。


        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {

            View row = convertView;
            if (row == null) {
                LayoutInflater inflater = getActivity().getLayoutInflater();
                row = inflater.inflate(R.layout.fragment_user_row, parent, false);
            }

            UserPojo userPojo = mViewList.get(position);

            ImageView userImage = (ImageView) row.findViewById(R.id.user_image);
            ImageLoader.ImageListener userImageListener = ImageLoader.getImageListener(userImage, R.drawable.image_loading, R.drawable.no_image);
            userImage.setTag(mImageLoader.get(userPojo.user_image_path, userImageListener));

            TextView userName = (TextView) row.findViewById(R.id.user_name);
            userName.setText(userPojo.user_name);

            TextView startTime = (TextView) row.findViewById(R.id.start_time);
            startTime.setText(userPojo.clime_start_time);

            userPojo = null;

            return row;
        }

上記をViewHolderパターンとCancelRequestパターンを実装して書き換えます。

リファクタリング後


        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {

            ViewHolder holder;
            if (convertView == null) {
                LayoutInflater inflater = getActivity().getLayoutInflater();
                convertView = inflater.inflate(R.layout.fragment_user_row, parent, false);
                holder = new ViewHolder(convertView);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            View row = convertView;
            if (row == null) {
                LayoutInflater inflater = getActivity().getLayoutInflater();
                row = inflater.inflate(R.layout.fragment_user_row, parent, false);
            }

            // リクエストのキャンセル処理
            ImageLoader.ImageContainer imageContainer = (ImageLoader.ImageContainer)holder.userImage.getTag();
            if (imageContainer != null) {
                imageContainer.cancelRequest();
            }

            UserPojo userPojo = mViewList.get(position);

            ImageView userImage = (ImageView) row.findViewById(R.id.user_image);
            ImageLoader.ImageListener userImageListener = ImageLoader.getImageListener(userImage, R.drawable.image_loading, R.drawable.no_image);
            userImage.setTag(mImageLoader.get(userPojo.user_image_path, userImageListener));

            holder.userName.setText(userPojo.user_name);
            holder.startTime.setText(userPojo.start_time);

            return convertView;
        }

        /**
         * View holder pattern
         */
        private class ViewHolder {
            ImageView userImage;
            TextView userName;
            TextView startTime;

            public ViewHolder(View view) {
                this.userImage = (ImageView) view.findViewById(R.id.user_image);
                this.userName = (TextView) view.findViewById(R.id.user_name);
                this.startTime = (TextView) view.findViewById(R.id.start_time);
            }
        }

リストの一行のviewをViewHolderクラスを作ってキャッシュし、Viewの再利用時にImageViewからImageContainerのインスタンスを取得し、リクエストのキャンセルを行っています。

リファクタリング前と比較すると相当軽く、速くなります。

p.s 2014年8月までならこれでリファクタリング終了ですが、android Lが使えるようになると、RecyclerViewを使ってさらに高速化できるようです。これは正式版がリリースされたら説明したいと思います。

参考サイト

この記事がお役にたちましたらシェアをお願いします

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

2014年10月4日土曜日

androidアプリ開発テクニックまとめ 画像編その2

前回のandroidアプリ開発テクニックまとめ 画像編その1の続きです。 今回は画像の回転処理についてまとめます。

環境

  • android:minSdkVersion="14"
  • android:targetSdkVersion="19"

画像処理 画像回転対策

androidは端末ごとにデフォルトのカメラが搭載されていますが、残念ながら画像を同じ向きで撮影しません。 例えば、Galaxy系はデフォルトが横ですが、nuxus系は縦になります(機種依存です。)

なので、アプリで全ての端末で画像を同じ向きで表示したい場合には画像の向きを取得する必要があります。 この画像の向きを取得するのにandroidではExifInterfaceというクラスを使います。

このクラスはクラス名の通り、Exif(イグジフと読みます)のタグを取得することができます。 Exif「イグジフ」は、画像に埋め込まれたカメラの機種や撮影時の条件を埋め込んでいるメタデータのことです。

では、以下にコードを記載します。


 /**
  * 画像の回転度を取得
  * @param filePath
  * @return
  */
 public static int getRotateDegree(Context context, Uri uri) {
  int degree = 0;
  try {
   ExifInterface exifInterface = new ExifInterface(getPath(context, uri));
   int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
   if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
    degree = 90;
   } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
    degree = 180;
   } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
    degree = 270;
   }
  } catch (IOException e) {
   degree = -1;
   e.printStackTrace();
  }

  return degree;
 }

 /**
  * 画像ファイルのパスを取得する
  * @param context
  * @param uri
  * @return
  */
 public static String getPath(Context context, Uri uri) {
  if(uri == null)
   return null;

  if(uri.getScheme().equals("file"))
   return uri.getPath();

  ContentResolver contentResolver = context.getContentResolver();
  String[] columns = { MediaStore.Images.Media.DATA };
  Cursor cursor = contentResolver.query(uri, columns, null, null, null);
  cursor.moveToFirst();
  String path = cursor.getString(0);
  cursor.close();
  return path;
 }

上記のようにすることで画像の向きを取得することが可能です。 上記のgetRotateDegreeメソッドで返り値が90, 180, 270の場合は、画像が返り値の度数だけ回転していることを示します。

画像を回転させるにはMatrixクラスを使います。


 /**
  * 画像が回転している場合は、適切に表示できるようなMatrixオブジェクトを返します。
  * 画像の回転が必要ない場合はnullを返す。
  * @param orientation 回転度数
  * @param width1      幅
  * @param height      高さ
  * @return
  */
 public static Matrix getMatrix(int orientation, int width, int height ) {

  Matrix matrix = new Matrix();
  if (orientation == 90) {
   matrix.postTranslate(-width/2,-height/2);
   matrix.postRotate(90f);
   matrix.postTranslate(width/2,height/2);
  } else if (orientation == 180) {
   matrix.postTranslate(-width/2,-height/2);
   matrix.postRotate(180f);
   matrix.postTranslate(width/2,height/2);
  } else if (orientation == 270) {
   matrix.postTranslate(-width/2,-height/2);
   matrix.postRotate(-90f);
   matrix.postTranslate(width/2,height/2);
  } else {
   return null;
  }

  return matrix;
 }

postRotateメソッドで回転度数を設定し、postTranslateメソッドで移動距離を設定しています。
移動→回転→移動と、画像を動かしています。

頭の中だけでイメージしにくい人は、紙に書き出して確かめるか、細かくデバッグして一つ一つのメソッドの動きを確かめてみましょう。 回転をさせる時に中心点をずらしているのがわかると思います。

また、Matrixは画像の拡大や縮小でも利用可能です。Bitmapのdecodeを使う場合がほとんどだと思いますが、Matrixと使い分けると便利です。

この記事がお役にたちましたらシェアをお願いします

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

2014年10月3日金曜日

2014/10月時の最新androidアプリ開発環境を構築

android開発の環境をまとめたので以下に記載します。android studioを使う場合は、最初に設定しておきましょう。のちのちの開発効率に天と地ほどの差がでてきます。 jenkinsなどのCIよりはるかに重要です。

環境

  • windows7
  • android studio v0.8.11

開発環境はandroid studioを利用します。今後、私はeclipseは使用しない方向です。
また、android studioでのandroidの開発環境は半年に一度は見直すとよいでしょう。できれば三か月に一度が理想だと思います。

android studioを最新の環境にupdate

android studioを立ち上げ、最新の環境にupdateします。

  • メニューバー → Help → check for update
  • update and Restart

android studioのメモリサイズを増やす

android studioをダウンロードしてきたばかりの初期設定では、動作が重すぎて使いものになりません。必ずメモリサイズを増やしてください。

  • C:\Users\{users}\AppData\Local\Android\android-studio\bin\studio.exe.vmoptionsをテキストエディタで開く。

-Xms1024m
-Xmx2048m
-XX:MaxPermSize=512m

上記のように値を変更します。マシンスペックに余裕があれば値をもっと増やすことを勧めます。

参考サイト

gradle.propertiesファイルを変更

プロジェクトルートにあるgradle.propertiesを編集します。コピペで良いと思います。


# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# The Gradle daemon aims to improve the startup and execution time of Gradle.
# When set to true the Gradle daemon is to run the build.
org.gradle.daemon=false

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true

# Enables new incubating mode that makes Gradle selective when configuring projects.
# Only relevant projects are configured which results in faster builds for large multi-projects.
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand
org.gradle.configureondemand=true

各設定はここここを参考にしてください。

gradleをオフラインモードに変更

毎回gradleで最新バージョンをチェックしにいくのを省略します。

  • メニューバー → Settings → Gradle
  • offline workをチェック

参考サイト

Logcatを見やすくする

Info を青に,Warning を黄色に,Error を赤にします。

  • メニューバー → Settings → Editor → Colors & Fonts → Android Logcat
  • Save Asで新規にSchemeを作成する。

参考サイト

ひとまずここまで、(多分)続く。

この記事がお役にたちましたらシェアをお願いします

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

2014年10月2日木曜日

unicornの学習メモ

railsで使用してるunicornのまとめ。毎回毎回調べるのが面倒なので。

環境

  • rails4.1.2 & centos6.5

unicornの起動


bundle exec unicorn_rails -c config/unicorn.rb -E development -D

-Dはデーモンとして起動
-Eは環境
-Cはファイル

unicornの停止


kill -QUIT `cat /tmp/unicorn.pid`

きもい停止方法や...

unicornの起動確認


ps auxf | grep unicorn | grep -v grep

設定ファイルメモ


listen "#{@app_path}/tmp/sockets/unicorn.sock", :backlog => 64
listen 8081, :tcp_nopush => true

listen

http://e-words.jp/w/E383AAE38383E382B9E383B3.html
通信機能を持つソフトウェアが、外部からのアクセスに備えて待機することをリッスンということがある。特に、TCP/IPの通信ができるOS上で動作しているプログラムが、特定のポート番号への接続を待ち受けることをリッスンということが多い。そのようなプログラムのことをリスナー(listener:聞き手、聴取者)ということがある。

backlog

listenで設定可能で、保留中の接続のキューの最大長

以上。

この記事がお役にたちましたらシェアをお願いします

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

vagrant by windows

windows環境でvagrnatで新しい環境を作成するたびに、同じエラーをやらかすので、以下に忘備録

環境

  • windows7
  • vagrant1.6.5
vagrant設定ファイル注意点

1. vagrant init {name}の{name}はvagrant box listで表示されるboxでなければならない。


  config.vm.box = "centos65-x86_64"

上記の設定が重要。boxがないbox名を指定すると落ちる。

2. エンコーディングはUTF-8に設定せよ!


  Encoding.default_external = 'UTF-8'

上記の設定がないと、vagrant内のコマンドでrubyなどのscript言語を実行すると落ちることがある。

3. GUIを有効にせよ!


  vb.gui = true

上記の設定がないと、VM Bootingで止まることがある。こうなるとOSの再起動が確定的。必ず設定すること。
以上

この記事がお役にたちましたらシェアをお願いします

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

2014年9月28日日曜日

2015年問題から見える日本のIT業界の問題点 その3

努力や学習でなく環境がエンジニアをつくる

常に人材不足だから、仕事には困らない。確かにそれは正しい。そして、散々叩かれているSI業界も今後10年は仕事に困らないのも、確実な未来だ。
人材不足ゆえ、もらえる賃金も上昇する可能性も高い。ボーナスは過去最高の数値をたたき出すかもしれない。 だが、ここで集められたエンジニアと他のプロジェクトで経験をしていくエンジニアは、能力という点で全く別の力を身につけていくことになる。

例えばあるエンジニアは、ネイティブでのアプリ開発がメインの仕事だ。vagrantとchefでサーバー環境を構築し、APIはrails&grapeで短時間で作る。 ユーザーを獲得するために機械学習の勉強もはじめている。コードだけを書いている訳ではない。毎日が新しい技術の勉強で、ユーザー獲得のためにトライアンドエラーの日々だ。悪くいえば何でも屋とも言う。

国家レベルの巨大プロジェクトで集められた人達は、毎日同じ作業だが、そのプロジェクトの中身に精通できる機会をもつことになる。巨大なシステムがどのように動いて、構成されているかを知ることはあとあと大きな財産になる。特に自社でシステムを組む時にそういった経験と知識は本当に役に立つ。漫然と開発だけして過ごすのではなく、ちょっとずつ全体を把握し、システムの規模によりどれくらいの人が動いているのかを理解するとよいだろう。

僕はどちらかが悪く、どちらかが優れているとかは思わないし、言う気もない。結局は自分がどういうキャリアを積みたいか、どういう仕事をしたいかということだ。これは人の価値観の問題である。誰もがtwitterで働くような年収10億のスーパーエンジニアを目指す必要はない。ただ、どの道を進んでも、この業界は進化していかないといずれ振り落とされてしまう。

そして、これがもっとも大切なことだが、あなたがどんなエンジニアになるかはあなたの努力や学習量よりも周りの環境が決める。
例えば僕の場合は、語学が大嫌いだったけど、今は英語と多少だけど中国語が使える。嫌々身に付けたスキルだけど、仕事以外のプライベートでも凄く役に立っている。技術も同じことである。労働時間の長い業界だから、努力や学習量よりも周りの環境で身に付くスキルが決まってしまうのだ。

自分がどんなエンジニアになりたいか、どういった人生を生きていきたいかについては、定期的に時間をとって冷静に考えてみる時間をもつと良い。 僕は三ヶ月ごとにその過去三ヶ月を振り返って、今満足しているかを確認しているが、これは役に立つ習慣だと思う。

道

自分の進んでいる道が間違っていると分かったら、すぐに道を変えるべきだ。新宿の歌舞伎町を目指して進んでいたら、秋葉原のメイドカフェに到着してしまいましたでは、目もあてられないだろう。幸いにもこの業界は、道を変えても、高速で新しい道を突き進める業界だ。チャンスはすぐに転がり込んでくるだろう。

この記事がお役にたちましたらシェアをお願いします

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

2015年問題から見える日本のIT業界の問題点 その2

人材不足が招く欠陥システムの量産

さて、2015年問題がなくとも、IT業界は常に人不足である。
人が少ないから業務時間は長くなり、仕事は過酷さを増し、人の入れ替わりが激しくなる。 人がいないのだから、人材派遣は悪だと国や国民が叫んだところで、人材派遣は必要悪で栄える。完全なる負のスパイラルだ。

僕は人買いビジネスが大嫌いだが、プロジェクトの納期がタイトで明らかに間に合わない場合は、やはり協力会社のエンジニアにヘルプを依頼することになる。納期を伸ばす交渉もするがやはり限界がある。これが、国や大企業の絡む大型案件ならなおさらだろう。彼らは優秀だが、前時代的なビジネスマインドだ。とりあえず誰でも良いから人をかき集めるようになるのは当然だ。

もちろんただでさえ人材不足な状況なのだから、ろくな人材が集まらないのは自明の理だ。さらに、どの会社もエース級は手放さない。内製のシステムを構築している会社であれば、エース級は社内にいてもらわないと困るのだ。こうして巨大プロジェクトのシステムの構築は迷走していく。

国や巨大企業が発注する巨大案件プロジェクトの開発現場の手法は、いわゆるウォーターフォールと呼ばれる手法を用いられることが多い。最初からビジネスの要件を100%満たせる仕様にまとめるあげるのは不可能なのに、この手法が採用されるプロジェクトは多い。理由はいつも同じだ。それは「(炎上した)実績があるから」である。
そして、引き継ぎに次ぐ引き継ぎと、伝言ゲームで伝わってきた不思議な設計書が実装者の手元に落ちてくるのだ。
そしてかき集められたプログラマー達は、その摩訶不思議な仕様書を眺めながら心にある疑念を抱きながらコードを書くのである。

「誰がこんなシステムを使うんだ。使いにくいにもほどがある」

仕様書に記載されているのは、一瞥しただけで使いにくいとわかるシステムだ。だが、彼らは仕事として割り切る。というか割り切る以外に方法はない。彼らは実装するのが主な仕事なのだ。悪臭のする香水を作れと言われて不思議に思ったとしても、その商品を作るのがプロだ。そしてその商品を売るのが営業で、使うのが運用だ。こうして欠陥システムが作られていくのである。

だが、さらにたちが悪いのはこの先である。前回、ダーウィンの言葉を引用させてもらった。

「唯一生き残るのは、変化できる者である」

何度もいうがこの言葉は絶対だ。きっとそれは未来でも変わらない。
集められたプログラマー達の中には辞めていく者も多いが、残る者も多いのだ。そう、彼らは「変化できる者」なのだ。
だが、その変化は悲惨な変化でもある。それは、「こういう仕様で、こういうシステムなのだ。」という変化を彼らは遂げるのだ。

彼らが環境に慣れた頃、新しい機能を実装するためにさらに多くのエンジニアが合流する。合流した彼らは開発環境を構築し、一通りの機能を触ってみる。そして眉をひそめて尋ねるのだ。

合流したエンジニア「このシステムは操作と手順を覚えるのが大変ですね(皮肉)」
変化したエンジニア「大丈夫ですよ。すぐ慣れます。慣れです、慣れ(苦笑)」
合流したエンジニア「うーん、ここの仕様がよくわからないんですけど」
変化したエンジニア「大丈夫ですよ。みんなよくわかってませんから。もちろん僕もわかりません。こういうもんなんです」

現場レベルで作業をしたことのないエンドユーザー様やコンサル様にはわからないだろう。これは経験した者にしかわからない。
だが、これは現実によくある話なのだ。というより巨大プロジェクトの5割の現場はこうである。まるで、IT業界には台本が用意されているかのように、このコントは上演される。

コント

こういった現場で働いたことが無い人も話には聞いたことはあるかもしれないが、本来の現場レベルは話に聞く100倍ひどいと思って間違いない。

こうして欠陥システムが次々と制作されていくのだ

この記事がお役にたちましたらシェアをお願いします

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

2015年問題から見える日本のIT業界の問題点 その1

IT業界では、2015年問題と呼ばれる課題が顔をのぞかせはじめているらしい。

2015年問題

アベノミクスによる景気回復に伴う情報システムへの投資拡大や、大規模プロジェクトの開発ピークの重なりによって、IT企業が2015年を中心にIT人材不足に悩まされている状況のこと
引用:http://www.itmedia.co.jp/enterprise/articles/1408/06/news012.html

「SIビジネスは崩壊する」
最近は、SIビジネスの問題があらゆる角度から色々と指摘され、もうSI業界に時間は残されていないような風潮ができあがっていた。

だが、その寿命が10年くらいは伸びたように思う。少なくともこの先5-10年は、人材派遣ビジネスでがっつりと金を稼げるだろう。オリンピック次第ではもっと長く稼げるかもしれない。
政治はビジネスのあり方をも大きく変えてしまう。アベノミクスは典型的な良い例だ。

アベノミクス

ダーウィンの
「最も強い者が生き残るのではなく、 最も賢い者が生き延びるでもない。 唯一生き残るのは、変化できる者である」
はこの世の真理をついている。

私は常駐型のエンジニアでないので、2015年問題にはあまり関心はなかった。しかし、最近は国内だけでなく中国の案件まで日本でやってくれと頼まれることが増えてきたので、そこまでエンジニアが足りてないのかと不思議に思い、ちょっと調査をしてみてこの問題を知ることになった。

そしてよい機会なので、僕なりにこの業界について考えていることや思っていることをまとめてみようと考えた。
この業界は、次から次へと新サービスが生まれてくる進化の早い業界だ。だが、実際は時代の流れに乗れて動けているのはごく一部なのだということを、IT業界以外の人も知っておこう。そして、IT業界にいるから決して時代の最先端にいるというわけではないということも。

この記事がお役にたちましたらシェアをお願いします

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

rails4 rspecリファクタリング

環境

  • rails(ruby2.1.0, rails4.0.3)
  • grape(API)

rails4でのrspecリファクタリングメモ。テストのコード量が増えてきたので、色々と試行錯誤してます。

リファクタリング前
FactoryGirlが重複しているコード。


      it "all request parameter is exists" do
        FactoryGirl.create(:user1)
        put "api/v1/user", {:id => user1.id}
      end
      it "xxx request parameter is exists" do
        FactoryGirl.create(:user1) 
        put "api/v1/user", {:id => user1.id, xxx => "aaaa"}
      end
      it "yyy request parameter is exists" do
        FactoryGirl.create(:user1)
        put "api/v1/user", {:id => user1.id, yyy => "aaaa"}
      end

リファクタリング後


      let(:user1) {FactoryGirl.create(:user1)}
      it "all request parameter is exists" do
        put "api/v1/user", {:id => user1.id}
      end
      it "xxx request parameter is exists" do
        put "api/v1/user", {:id => user1.id, xxx => "aaaa"}
      end
      it "yyy request parameter is exists" do
        put "api/v1/user", {:id => user1.id, yyy => "aaaa"}
      end

letを使うことで、lazy loadしてspecテストが終わるまでuser1変数をキャッシュとして使っています。
ただし、変数として必要ない場合はletでなくbeforeを使ってテストデータを挿入してます。理由は僕のプロジェクトの場合、dbcleanerでテストごとにマスタ以外のテーブルを空にしているからです。

しかし、rspecでコードをdryに保つのは本当に大変です。この辺のかける時間と必要性のバランスは本当に永遠の課題ですね。

参考サイト

この記事がお役にたちましたらシェアをお願いします

このエントリーをはてなブックマークに追加
Related Posts Plugin for WordPress, Blogger...