SlickでANDとORを混在させたクエリを発行する

例えばこんなSQLを発行したかったりします。

select * from cities
where (population > 100000) and (class = '都' or class = '府')

Slickではこんな感じで書けます

Cities.filter(row => row.population > 100000 && ( class === '都' || class === '府' ))


“AND” は “&&”、”OR” は “||” で記述し、必要な箇所でカッコを記述すれば目的のクエリとなってくれました。

これが動的になった場合にはどうするか?
例えば 下記のSQLで都道府県の抽出が可変の場合(inで書けってのは無しで・・)

select * from cities
where (population > 100000)
  and (class = '都' or class = '府' or class = '道' or class ='県')

試行錯誤した末にfunctionを作って対応しました。

def classConditions(row:Cities,t:Boolean,d:Boolean,f:Boolean,k:Boolean):Rep[Boolean] = {
  var condition:Rep[Boolean] = true
  if (t) condition = condition || (row.class === "都")
  if (d) condition = condition || (row.class === "道")
  if (f) condition = condition || (row.class === "府")
  if (k) condition = condition || (row.class === "県")
  condition
}

Cities.filter(row => row.population > 100000 && ( classConditions(row,true,true,false,false) ))

このへんの情報がなかなか見つからずに苦労します。

[Play] dist時に独自のリソースフォルダを加える

Play frameworkで作成したアプリケーションを公開する際に、”dist”コマンドを実行してデプロイ用のzipを作成すると思いますが、そのまま処理すると独自に追加したリソース用のディレクトリが含まれてくれません。

そういった場合にはbuild.sbtに追記します。

例えば プロジェクトフォルダ直下に”templates”というディレクトリを作成していたとしたら、
build.sbtに次のコードを追記します。

// templatesフォルダとその内容をdist対象に追加
mappings in Universal ++= {
  val templateDirectory = baseDirectory(_ / "templates").value
  val templateDirectoryLen = templateDirectory.getCanonicalPath.length
  (templateDirectory ** "*").get.map { f: File =>
    f -> ("templates/" + f.getCanonicalPath.substring(templateDirectoryLen))
  }
}

MKMapViewのコンパスを移動させる

MapViewを回転(方角を変更)すると右上にコンパスが表示されます。
こんなの↓
20150727_1

右上に表示されるもんですから画面上部にツールバーをフロートで表示させようとした時などにとっても邪魔!
しかもこれ、iOS9だと消せるらしいのですがiOS8だと消せないんです(たぶん)。
で、移動させちゃえって思ったのですが、そんなプロパティ見つかりませんし、、

ただ、一部のアプリでは標準と違う場所に表示されているので何かしら方法があるのではないかと思って色々調べていたらstackoverflowでこんな情報が見つかりました。

http://stackoverflow.com/questions/18903808/ios7-compass-in-mapview-placing

簡単に説明すると

  • MKMapviewはUILayoutSupportが示す位置を参考にしてコンパスとか著作権情報などの表示位置を決めている。
  • で、このUILayoutSupportってのはViewControllerのtopLayoutGuideとかbottomLayoutGuideで決める事ができる。
  • なので、いい感じの位置情報を返すUILayoutSupportを、topLayoutGuideとかbottomLayoutGuideで返せばOK!

 
 
実際のコードはこんな感じで

最初に UILayoutSupport を実装したクラスを定義します

class MapLayoutGuide: NSObject,UILayoutSupport {
    
    private var _length:CGFloat = 0;
    
    init(length:CGFloat){
        super.init();
        _length = length;
    }
    
    var length: CGFloat {
        get {
            return _length
        }
    }

    @available(iOS 9.0, *)
    var topAnchor: NSLayoutYAxisAnchor {
        return NSLayoutYAxisAnchor()
    }
    
    @available(iOS 9.0, *)
    var bottomAnchor: NSLayoutYAxisAnchor {
        return NSLayoutYAxisAnchor()
    }

    @available(iOS 9.0, *)
    var heightAnchor: NSLayoutDimension {
        return NSLayoutDimension()
    }
}

で、これを ViewController の topLayoutGuide で返すようにオーバーライド

override var topLayoutGuide: UILayoutSupport {
   get {
      return MapLayoutGuide(length: 50);
    }
}

するとコンパスが下図のように移動してくれます。

20150727_2

(2015.09.15) iOS9向けのコードを追加しました

ランニングコース作成アプリ「RouteDesigner」

姉妹ブログの方でも紹介させてもらっていますが、ランニングコース作成アプリ「RouteDesigner」をリリースしました。
もちろん全面Swiftで作成してます。
対応OSはちょっと迷いましたが、今後のメンテナンスを考えてiOS8以降としました。
すりガラス効果使いただけなんですけどね。

ランニングやサイクリングに興味のある方は是非見てみて下さい。
紹介エントリ

AppStoreはこちら

NSStringからnull文字を削除

デバイス制御系の処理を書いているとデバイスのSDKから渡されたNSStringが変な動きをすることがあります。
stringWithFormatで処理しても途中で切れたり、NSMutableStringのappend系で追加しても切れたり。。
私が直面した現象の原因は単純で、null文字がNSStringに含まれていることなんですけど、なんつーかFucking!ですよね。

いくら舌打ちしたところでSDKを修正してもらえるわけではありませんので、対応を。

下記のコードでNSStringに含まれているnull文字を削除できます。

-(NSString*)trimNullString:(NSString*)val
{
  if (!val) return nil;

  NSInteger length = [val length];
  unichar buffer[length];
  [val getCharacters:buffer range:NSMakeRange(0, length)];

  int endpos = 0;
  for (int i=0; i<length; i++) {
    unichar c = buffer[i];
    if (c == 0x0){
      endpos = i;
      break;
    }
  }

  if (endpos > 0){
    NSString *result = [NSString stringWithCharacters:(const unichar *)buffer  length:(NSUInteger)endpos];
    return result;
  } else {
    return @"";
  }
}

“AWS SDK for Java”を使ってCloudSearchから検索結果を取得する

CloudSearchの検索時に”AWS SDK for Java”を使ってみるサンプルです。
HttpClient等を使った例は良く見かけますが、SDKを使った例が見つからなかったので載せときます。

scalaでの記述例ですけど、javaでもほとんど同じですよ。

import com.amazonaws.auth.AWSCredentials
import com.amazonaws.services.cloudsearchdomain.AmazonCloudSearchDomainClient
import com.amazonaws.services.cloudsearchdomain.model.SearchRequest
import com.amazonaws.services.cloudsearchdomain.model.QueryParser
import com.amazonaws.services.cloudsearchdomain.model.SearchResult


// search用のエンドポイント
val endpoint = "search-testdb-xxxxxxxxx.ap-northeast-1.cloudsearch.amazonaws.com"

// クライアントを準備
val credentials = new BasicAWSCredentials("accessKey", "secretKey")
val client = new AmazonCloudSearchDomainClient(credentials)
client.setEndpoint(endpoint)


// リクエストを生成
// 各項目に設定する内容はhttpクライアント等で search apiを実行する時に
// 設定する内容とだいたい一緒です  
// 詳しくは↓
// http://docs.aws.amazon.com/cloudsearch/latest/developerguide/search-api.html
//
var request = (new SearchRequest())
	.withQueryParser(QueryParser.Simple)
	.withQuery("検索ワード")
	// titleとsubtitleに対して"AND"検索を行う	
	.withQueryOptions("{\"defaultOperator\":\"and\",\"fields\":[\"title\",\"subtitle\"]}")
	// 先頭から1000件まで取得	
	.withStart(0)
	.withSize(1000)
	// ソート条件を設定
    .withSort("sortkey1 asc,sortkey2 desc")
    // 結果として取得する項目
    .withReturn("_score,title,ranking")
    

try{
    // 検索処理
    val result = client.search(request)
	
	 
    val status  = result.getStatus()
    val hits = result.getHits()
    
    // インデックス全体でのトータル件数(startとsizeで指定した範囲外も含む)
    val total = hits.getFound();
    
    // 取得したレコードをマップ (javaな人は for-eachしていると思って下さい)
    hits.getHit().map{rec => 
    
      // ID項目
      val id = rec.getId()
      
      // Fieldを参照してみる
      val ranking = rec.getFields().get("ranking").get(0)
      
      Logger.info(" id:" + id + " ranking:" + ranking)
    }

} finally {
   client.shutdown()
}

なぜSDKを使う必要があったかと言うと、IAMロールでのアクセス制限を行いたかったからです。
署名済リクエストを作るのがやたらと面倒だったので・・・
SDKを使う事で簡単に署名済のリクエストが行えます。

“AWS SDK for Java”を使ってCloudSearchへデータを登録する

前回の検索に続いてこんどはドキュメントの更新処理です
これもSDKを使ったサンプルが見つからないんですよね。。。探し方が悪いのかな??

scalaでの記述になっていますけど、javaでもほとんど同じです。

import com.amazonaws.auth.AWSCredentials
import com.amazonaws.services.cloudsearchdomain.AmazonCloudSearchDomainClient
import com.amazonaws.services.cloudsearchdomain.model.UploadDocumentsRequest
import com.amazonaws.services.cloudsearchdomain.model.ContentType
import java.io.ByteArrayInputStream


// document用のエンドポイント
val endpoint = "document-testdb-xxxxxxxxx.ap-northeast-1.cloudsearch.amazonaws.com"

// クライアントを準備
val credentials = new BasicAWSCredentials("accessKey", "secretKey")
val client = new AmazonCloudSearchDomainClient(credentials)
client.setEndpoint(endpoint)

// データ投入用のjsonデータを作成する
val datajson = "[{ ...省略... }]"

// json文字列をbyte配列としてInputStreamを取得
val postdata = datajson.getBytes()
val data = new ByteArrayInputStream(postdata); 

// リクエストを生成
var request = (new UploadDocumentsRequest())
    .withContentType(ContentType.Applicationjson)
    .withContentLength(postdata.length)
    .withDocuments(data)
     
// uploadを実行
val response = client.uploadDocuments(request)

// 何件登録して、何件削除したのかを確認できます      
Logger.info(response.toString())

client.shutdown()

datajsonに入れる文字列はHttpクライアント等で “documents/batch” APIにpostする内容と同じです
詳しくは↓
http://docs.aws.amazon.com/cloudsearch/latest/developerguide/document-service-api.html

簡単なサンプルですとこんな感じです

[
  { 
  	"fields": { "title": "タイトル100","subtitle": "サブタイトル" }, 
	"id": "100", 
	"type": "add"
  }, 
  {
  	"fields": { "title": "タイトル101", "subtitle": "サブタイトル" }, 
	"id": "101", 
	"type": "add"
  },
  {
  	"id": "900",
  	"type": "delete"
  }
]

jsonはJacksonとかGsonとかで作ると楽ですね。

iOS8で NSDecimalNumber が変な値を返してくる!(怒)

iOS8上でなぜか変な挙動を示すアプリがあったので原因を調査してみたところ、NSDecimalNumber の integerValue や longValue が変な値を返すようになっていた。

検証コード

NSDecimalNumber* n = [NSDecimalNumber decimalNumberWithString:@"123.123456789012345678"];
if ([n intValue] !=[n integerValue]){
    NSLog(@"What?!");
    NSLog(@"decimal %@", n);           
    NSLog(@"int %d", [n intValue]);
    NSLog(@"integer %ld",[n integerValue]);
    NSLog(@"long %ld", [n longValue]); 
    NSLog(@"longlong %lld", [n longLongValue]);
    NSLog(@"uinteger %ld",[n unsignedIntegerValue]);
 } else {
    NSLog(@"success!!");
 }

このコードを実行すると iOS7.1(64bit)では次のように素直な値が表示されます。

decimal 123.123456789012345678
int 123
long 123
longlong 123
integer 123
uinteger 123

でもこれがiOS8だと・・
64bit環境では
decimal 123.123456789012345678
int 123
integer -6
long -6
longlong -6
uinteger 12

32bit環境では
decimal 123.123456789012345678
int 123
integer 0
long 0
longlong -6
uinteger 0

って結果となる。なんでやねん!!

NSDecimalNumberを”123.123456″とかで初期化するとこの問題は発生しないんだよね。
桁数の問題であれば NSDecimalNumber を初期化する際にまるめておけば(有効桁数が十分なのであれば)解決するんだけど、どうだんんだろ?

なんにせよ、iOS8のバグだよね。きっと。

XCode6-beta2で作ったAD-Hoc用ipaファイルがインストール出来ない件

まぁ表題どおりなんですけど。
itunesからインストールしようとしても、WEBからインストールしようとしてもダメなんです。
なんでかな〜とGoogle先生にたずねていたらこんな方法で対応できるようです。

/adhoc_release/testapp.ipa がXCodeから出力されたipaファイルだとすると
(パスは適当に権限のあるところでね)

ditto -xk /adhoc_release/testapp.ipa /adhoc_release/temp
ditto -ck –norsrc /adhoc_release/temp /adhoc_release/testapp_repacked.ipa
rm -r /adhoc_release/temp

これで出力された testapp_repacked.ipa を testapp.ipa にリネームしてインストールするとOK