firebase_storageでPermission denied対応

firebase_storageの扱い方で数時間を溶かしたので、同じような人が出ない為にまとめました。

TL; DL

よくない例

FirebaseStorage _storage = FirebaseStorage(app: myapp);
final ref = _storage.ref().child(dirname).child(filename);

よい例1

FirebaseStorage(app: myapp);
final ref = FirebaseStorage.instance.ref().child(dirname).child(filename);

よい例2

FirebaseStorage(app: myapp);
final _storage = FirebaseStorage.instance;
final ref = _storage.ref().child(dirname).child(filename);

firebase_storageを使う

FlutterはFirebaseと親和性が高く、Firebaseのあれこれが簡単にできます。
FirebaseStorageへのアクセスも公式からパッケージが準備されているので、それをpubspecに記載して、コードを書くだけ。
(build.gradleを修正する必要はありません)
かんたん!!easy!!

と思っているとハマりました。

firebase_storageのインストール

pubfirebase_storageと検索をかけると出てきます。

pub.dev

Usage To use this plugin, add firebase_storage as a dependency in your pubspec.yaml file.

簡潔すぎる気もしますが、firebase_storageを使うための準備は実際これだけ。

dependencies:
  flutter:
    sdk: flutter
  firebase_core:
  firebase_auth:
  firebase_storage: # <= added!

Firebaseを使うためにはfirebase_coreが必要なのと、Storageへのアクセス権限を限定したい場合はfirebase_authも必要です。
追記したらflutter packages get

ConsoleでStorageの権限を確認

Firebase Console「開発」メニュー内にある「Storage」を選択、「ルール」タブを表示します。
デフォルトはこんな感じ。

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

認証済みだったら誰でも読み書きできるよ、という設定。
疎通確認が目的なのでこのまま進めます。

Permission deniedにぶつかる

とりあえずこんな感じでコードを書いてみます。

import 'dart:io' show File;
import 'package:intl/intl.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart';

class StorageApi {
  // myappはFirebaseAppのインスタンス
  FirebaseStorage _storage = FirebaseStorage(app: myapp);

  Future<String> uploadImage(File file) async {
    final timestamp = DateFormat('yyyyMMddHHmmss').format(DateTime.now());
    final ref = _storage.ref().child('images').child(timestamp);
    final uploadTask = ref.putFile(file, StorageMetadata(contentType: 'image/jpeg'));
    final snapshot = await uploadTask.onComplete;
    if (snapshot.error == null) {
      return await snapshot.ref.getDownloadURL();
    } else {
      print(snapshot.error);
      return null;
    }
  }
}

FirebaseにログインしたユーザでuploadImageを叩いてみると、Permission denied
こんな感じのエラーが続きます。

E/StorageUtil(25832): error getting token java.util.concurrent.ExecutionException: com.google.firebase.internal.api.FirebaseNoSignedInUserException: Please sign in before trying to get a token.
W/NetworkRequest(25832): no auth token for request
E/StorageException(25832): StorageException has occurred.
E/StorageException(25832): User does not have permission to access this object.
E/StorageException(25832):  Code: -13021 HttpResult: 403
E/StorageException(25832): The server has terminated the upload session

ログインはしてるはず、と思ってcurrentUserをprint。

final currentUser = await FirebaseAuth.instance.currentUser();
print(currentUser);

返ってきます。ログイン済。

他に原因があるのかと思って、Storageのルールを全許可にすると、アップロードできます。
ということは、エラーメッセージ通り認証がうまくいっていないということ。

………ん?🤔

対処法

調べても出てこない!
同様に悩んでいる人は多いのですが、ルールを全許可にすればよいよ、という回答があるばかり。
違う、やりたいことはそうじゃない。

調べ続けて出てきたのがこのIssue

In fact looking into some of the related issues of the one you mentioned I managed to find a workaround which is simply access the storage with FirebaseStorage.instance

また、このIssueも。

Not Works

final FirebaseStorage storage = FirebaseStorage(app: app, storageBucket: '...');

Works

final FirebaseStorage storage = FirebaseStorage.instance;

つまり、こういうことか…!

class StorageApi {
  // FirebaseStorageはここではinitializeするだけ
  StorageApi() {
    FirebaseStorage(app: myapp);
  }

  Future<String> uploadImage(File file) async {
    final timestamp = DateFormat('yyyyMMddHHmmss').format(DateTime.now());
    // `FirebaseStorage.instance`にアクセスする
    // 変数に保持したい場合は`FirebaseStorage.instance`を入れる
    final ref = FirebaseStorage.instance.ref().child('images').child(timestamp);
    final uploadTask = ref.putFile(file, StorageMetadata(contentType: 'image/jpeg'));
    final snapshot = await uploadTask.onComplete;
    if (snapshot.error == null) {
      return await snapshot.ref.getDownloadURL();
    } else {
      print(snapshot.error);
      return null;
    }
  }
}

どういうこと?

FirebaseStorageはシングルトンなので、一度initializeすると常に同じオブジェクトが返ってきます。

class FirebaseStorage {
  FirebaseStorage({this.app, this.storageBucket}) {
    if (_initialized) return;
    channel.setMethodCallHandler((MethodCall call) async {
      _methodStreamController.add(call);
    });
    _initialized = true;
  }
  // ...(略)
  static bool _initialized = false;
  static FirebaseStorage _instance = FirebaseStorage();
  static FirebaseStorage get instance => _instance;
  // ...(略)
}

が、FirebaseStorage(app: myapp)で返ってくるインスタンスFirebaseStorage.instance(FirebaseStorage())で返ってくるインスタンスは違っていて、使うべきは後者だった、ということでした。

FirebaseAuth.instanceでアクセスするのが布石だったな…という反省。
お疲れ様でした。

Ruby2.7の新機能PatternMatchingが最高でした

RubyKaigi2019で聞いたRuby2.7から入るPattern Matchの機能に感動したのですが、セッション中は理解しきれない部分があったので、スライドを読み、コードを動かしてみました。
そしたら改めて感動した、という記事です!

speakerdeck.com

スライドのはじめに、下記の記載があります。

  • PatternMatchingは2.7.0からの新機能ですが、trunkにはもうcommit済
  • 仕様はまだ策定中
  • 試してフィードバックくださいね!

なお、githubにサンプルコードを置いています。

1. 準備

Ruby2.7.0(dev)はビルドしなきゃかな…と思ってたらrbenvがもう対応してました。
はやい!うれしい!

$ brew upgrade rbenv ruby-build

これで無事2.7.0-devがリストに出てくるようになります。

$ rbenv install --list
  (略)
  2.6.1
  2.6.2
  2.6.3
  2.7.0-dev
  (略)
  
$ rbenv install 2.7.0-dev
(略)
Installed ruby-trunk to /Users/makicamel/.rbenv/versions/2.7.0-dev

$ rbenv versions
* system
  2.7.0-dev

$ rbenv local 2.7.0-dev

$ rbenv version
2.7.0-dev (set by /Users/makicamel/pattern_match/.ruby-version)

これで準備完了です!

試しにPatternMatchingを書いてみると動くようになりました!
この時warningが出るのですが、ほんと開発中って感じが、わくわくします…!

warning: Pattern matching is experimental,
and the behavior may change in future versions of Ruby!

2. PatternMatchingとは?

case句に対して複数の値を割り当てられること。

従来のcase句ってこんな感じ。完全一致です。

case [0, [1, 2, 3]]
when [0]
  :unreachable
when [0, [1]]
  :unreachable
when [0, [1, 2, 3]]
  p 'here'
end
# => here

PatternMatchingが導入されるとこうなります。(in節になります)

case [0, [1, 2, 3]]
in [0, [1]]
  :unreachable
in [a, b]
  p a # => 0
  p b # => [1, 2, 3]
in [0, [1, 2, 3]]
  :unreachable
end

パターンマッチされるだけでなく、マッチした変数名でそのままマッチした値が取り出せます。

*(スプラット演算子)を使ってこんな風にもかけます。

case [0, [1, 2, 3]]
in [a, [b, *c]]
  p a # => 0
  p b # => 1
  p c # => [2, 3]
end

ちゃんと構造もチェックしてくれるので、こうなります。

case [0, [1, 2, 3]]
in [a]
  :unreachable
in a
  p a # => [0, [1, 2, 3]]
end

ハッシュも使えます。

case {a: 0, b: 1}
in {a: 0, x: 1}
  :unreachable
in {a: 0, b: var}
  p var # => 1
end

すごい使い所ありそう!
セッションによると、JSONのデータを扱う時に便利ですよ、とのこと。

person = '{
  "name": "Alice",
  "children": [
      {
          "name": "Bob",
          "age": 2
      }
  ]
}'

case JSON.parse(person, symbolize_names: true)
in {name: 'Alice', children: [{name: 'Bob', age: age}]}
  p age # => 2
end

シンプル!!

3. 仕様について

Syntax

  1. 最初にマッチするまで実行される
  2. どのパターンもマッチしない場合、else 節が実行される
  3. どのパターンもマッチせずelse節もない場合、NoMatchinPatternErrorが発生する。

1と2は現状のcaseと同じですね。
どの条件にも一致しない場合は例外が発生するので、パターンマッチを使う時には網羅性を確認してください、とのことでした。

4.if/unlessで条件づけができる

case [0, 1]
in [a, b] unless a == b
  p a # => 0
  p b # => 1
end

a, bが先に評価され、マッチした時にif/unlessが評価されます。
なので、マッチした値を利用した評価が可能。すごい!

Pattern

1. ValuePattern

case/whenと同様に、===で評価されます。

case 0
in 0
in -1..1
in Integer
end

どれともマッチします。whenと同じ挙動。

2. VariablePettern

先程までの例のように値をマッチさせ、その変数と値が結び付けられます。
また、_を使うと値を捨てて、ワイルドカード的に使えます。

case [0, 1]
in [_, _]
  :reachable
end

注意点としては、in節で変数を使うと、case句の外で変数を定義していてもパターンマッチされてしまうこと。
パターンマッチではなく定義された変数として使いたい場合は^を使います。

a = 0
case 1
in a
  p a # => 1
end

a = 0
case 1
in ^a
  :unreachable
end
# => NoMatchingPatternError

3. AlternativePattern

case文を書き始めてしばらくして、こんな感じで書けたら…と思った記憶があります。
現実になった!すごい!

case 0
in 0 | 1 | 2
  :reachable
end

4. As Pattern

パターンがマッチした時、=>で変数名を指定することで変数と値を結び付けられます。
この使い勝手のよさがすごい。
ValuePatternだけじゃなくて、他のパターンでも使えます。

case 0
in Integer => a
  p a # => 0
end

case 0
in 0 | 1 | 2 => a
  p a # => 0
end

case [0, [1, 2]]
in [0, [1, _] => a]
  p a # => [1, 2]
end

5. ArrayPattern

ArrayPatternとはいうけれど、Array以外でも使えます。
以下の3つを満たす時にマッチします。

  1. Constant === objectの時
  2. objectがArrayを返すdeconstructメソッドを持つ時
  3. ネストしたobjectが2の条件を満たす時

また、パターンの書き方は下記のいずれも可能です。

Constant(pattern, pattern, ..., *var, pattern)
Constant[pattern, pattern, ..., *var, pattern]
[pattern, pattern, ..., *var, pattern]
case [0, 1, 2]
in Array(0, *a, 2)
in Object[0, *a, 2]
in [0, *a, 2]
in 0, *a, 2 # `[]`は省略できる
end
p a # => [1]

この4つは全て同じ結果。
ふと気になったのが、in [0, *a, 2]って最初の例と同じ。
deconstructメソッド実装しなくていいの?と思って確認してみると、[].methods.include? :deconstruct # => trueでした。
Ruby2.6.2では[].methods.size188、2.7.0-devでは189になっていて、おお…となりました。

さて、先述した通り、ArrayPatternはArray以外でも使えます。
objectがArrayを返せばOKなので、実装してあげます。

class Struct
  alias deconstruct to_a
end

Color = Struct.new(:r, :g, :b)
color = Color[0, 10, 20]
p color.deconstruct # => [0, 10, 20]
case color
in Color[0, 0, 0]
  p 'black'
in Color[255, 0, 0]
  p 'red'
in Color[r, g, b]
  p "#{r}, #{g}, #{b}"
end

StructがArrayを返すよう、deconstructto_aエイリアスに設定しています。

スライドにはASTの例もありましたが、理解が追いつかないのでレベルアップしてから再挑戦します。

6. HashPattern

HashPatternも、Hash以外でも使えます。
以下の3つを満たす時にマッチします。

  1. Constant === objectの時
  2. objectがHashを返すdeconstruct_keysメソッドを持つ時
  3. ネストしたobjectが2の条件を満たす時

また、パターンの書き方もArray同様下記のいずれも可能です。

Constant(id: pattern, id: pattern, ..., **var)
Constant[id: pattern, id: pattern, ..., **var]
{id: pattern, id: pattern, ..., **var}

Arrayに引き続き確認すると、{}.methods.include? :deconstruct_keys # => true
ただし今回はRuby2.6.2では{}.methods.size174、2.7.0-devでは176
ひとつはdeconstruct_keysとして、もうひとつは?と確認してみると、:tallyでした。

tally配列の要素数を要素毎に数え上げるEnumerableモジュールのメソッド
こちらも楽しみです😊

さて、コードを見てみます。

case {a: 0, b: 1}
in Hash(a: a, b: 1)
  p a # => 0
in Object[a: a]
in {a: a}
in {a: a, **rest}
  p rest # => {b: 1}
end

{}は省略可。変数名も省略できます(a: == a: a)。

case {a: 0, b: 1}
in a:, b:
  p a # => 0
  p b # => 1
end

また、deconstruct_keysの実装で思いがけない値を返すと逆効果になるので、実装に注意してくださいとのことでした。

  • deconstruct_keysに渡されるkeysはパターンに含まれるkeyの配列
  • keysに含まれていないkeyは無視してOK
  • **restがパターンに含まれる場合はnilが渡る
  • その場合、全てのkey-valueセットを返さなければならない

コードを見たほうがわかりやすそうです。

class Time
  VALID_KEYS = %i(year month)

  def deconstruct_keys(keys)
    if keys
      (VALID_KEYS & keys).each_with_object({}) do |k, h|
        h[k] = send(k)
      end
    else
      {year: year, month: month}
    end
  end
end

now = Time.now # 2019-05-07 ...
case now
in year: # now.deconstruct_keys([:year])を呼ぶ
  p year # => {year: 2019}
in **rest # now.deconstruct_keys(nil)を呼ぶ
  p rest  # => {year: 2019, month: 5}
end

ArrayとHashの違いで気をつけたいのは、Arrayは完全一致、Hashはサブセットマッチなこと
ArrayとHashでは使い所が違うから、とのことでした。

case [0, 1]
in [a]
  :unreachable
in [a, *]
  :reachable
end

case {a: 0, b: 1}
in {a: a}
  :reachable
end

デザイン

Historyは時間がなくてセッション中端折られてしまったのですが、スライドによると2012年にgemを作ったことから端を発して、7年かけて作られた機能。
最初はmatch/patternだったりcase/=>になっていったり、時間をかけて様々考えられ、磨きぬかれていった様にわくわくします!

キーワードも、matchでなくcaseしたのは、matchだと既存のコードを壊してしまう可能性があるから。
新しい予約語を使わないために最初は記号を使ったりしようとしていたのだけど、そうだ、僕らにはfor/ininがあるじゃないか!と思いついた話がすごく好きです。

なるべく自然に書けるように、でも既存のコードを壊さないように。
コードを試し書きしていて何度も気持ちいいなあ、と思ったのですが、Rubyの「開発者が楽しい」ってこうして守られ、作られているんだなあ、と改めて感じ入りました。

また、まだもっとよくしたいんだけど、どう思う?というスライドもたくさんあって、わくわくしました。

パターンマッチング、楽しみです!

RubyKaigi2019に行ってきました

すっかり遅くなりましたが、先月、念願のRubyKaigiに行ってきました!

rubykaigi.org

RubyKaigiは毎年開催されています。
実は去年の仙台も行こうかどうしようか迷って、「わたしにはまだ早いか…」と行くのを取りやめたのでした。
今年は初参加してすごく良かったので、思ったこと・感じたことを書いてゆきたいと思います。

「つよい」人の為だけじゃないRubyKaigi

RubyKaigiは完全テックで、「つよい」人のためのカンファレンス。
だから、わたしみたいなひよっこなんかが参加しても。。。と思っていました。
今年参加して思ったのは、テックであることに間違いはないんですが、つよい人のため、は間違い。
文系の学生さん、エンジニア歴数ヶ月の方、事務職でこの間始めてRails触ったよって人、いろんな方がいらしていて、でも、みんなそれぞれ自分のRubyKaigiを楽しんでいました。
わたしは、セッションのうち理解できたのはたぶん10分の1くらい。
なのだけど、ほんと楽しかった!!!

楽しかった理由はたくさんあるんですが、大きくわけて、みっつだなあと思います。

  • 人を感じられたこと
  • RubyKaigiはおまつり
  • 人と参加する

人を感じる

RubyKaigiに行って強く感じたのは、「この方々がRubyを作ってるんだなあ」ということ。
オーガナイザーの松田さんもおっしゃっていたのですが、「ソフトウェアに血が通う」という感じ。

RubyOSSのソフトウェアたちって、すごい人たちがすごい才能とすごい技術でもって華麗に作ってるもの、みたいな、恥ずかしながら、そんなイメージを持っていました。

当たり前だけど現実は、すごいことには違いないけど、地道な仕事の積み重ね。
(というか、それがすごい)

例えばささださんのセッション

http://www.atdot.net/~ko1/activities/2019_rubykaigi2019.pdfwww.atdot.net

現在のRubyはほとんどがCで書かれているけれど、Rubyで書いたほうがよいところもあって、それをRubyで書けるような仕組みを導入しよう、というご提案。

この中で「CはRubyよりも大体の場合において早いけど、Rubyのほうが早い場合がある」という指摘があります(キーワード引数や例外処理はCよりRubyの方が早い)。
セッション中、こういう観点、この粒度で見て、こうやってRubyってよくされていくのかー!と心の中で叫んでいました。

また、WatsonさんのRMagickについてのセッション

speakerdeck.com

RMagick(画像処理用のGem)に色んな問題(インストールでハマるとか)があって、その問題に対処しました、というお話。
※ なお、このお話の前日談はこちら(2018年秋の大江戸Ruby会議07でのお話)。

わたしが初めてRMagickをインストールした時もハマったし、「Ruby RMagick インストール」で調べるとハマってる人がたくさんいるし、そういうものなんだなあと思っていたのですが、「当たり前じゃない、直すべき負債」として淡々と直していく姿に感謝と尊敬の念を持ちました!

なんといっても尊かったのは、2日目のながちかさんのKeynote

www.slideshare.net 「Be-Practical!(現実的な判断をしましょう!)」

ながちかさんはRuby安定版のメンテナをしていらっしゃる方。
trunkの日々の修正コミットを見て、それが必要か判断して、必要なら安定版にbackportするんですが、その判断が難しい、とおっしゃっていました。
が、修正コミットを日々見るだけでもものすごいこと。
その判断も、バグ修正と新しい機能って簡単には切り分けられないし、「魔境」があったりバグを修正して新たなバグを生むことになったり、「きれいで気持ちのいいコミットでついbackport」してしまいrevertしたお話とかがあって、日々当たり前のように使わせて頂いている安定版は、こうやって作られているんだなあ…と頭が下がる思いでした!

ただ、この後、松田さんとお話をする機会があって、「ありがたい」と思ったことをお伝えしたら、少し首をかしげられました。
「ゴミが落ちてたら拾うじゃないですか。当たり前のことをやってるんで、あんまりありがたいと言われても、少し違うんですよね」

みんな楽しそうに、あるいは面白がってやっているから格好いいなあと思うし、わたしもそうなりたい…!と強く感じました!

RubyKaigi2019はほんとうにおまつりだった

セッション自体は難しいけれど、気難しさはまったくなくて、笑いや歓声や拍手の絶えないセッションもあり、質疑応答でも茶目っ気たっぷりなやりとりが交わされたりもしていました。

スポンサーブースもたくさん立っていたのですが、みなさん真剣に面白がりにきている。
その最たるものが、お昼ごはんと懇親会のヤバさ。
(ちなみに、朝昼夜と食事が準備されるので、ほとんどお金使いませんでした)

お昼ごはんには、国際会議場のお庭に屋台(本物の屋台)が出現。
0日目にはナイトクルージングしたり一蘭貸し切ったりと、おまつり感がほんとすごい。
(始まっていないのにRubyKaigi満喫してる感がすごかった)

1日目の懇親会は商店街を貸し切り!!!(テレビでニュースになったそうです)
商店街貸し切りは最高で、ふら〜っと歩いては飲み、また歩いては食べ、という感じで、知っている人とも知らない人ともたくさん話してたくさん笑いました。
何気なく話しかけた方がコミッターの方だったり(勉強不足ですみません)、貸し切りとはいえ一般の方々も歩いていたり、全体的にカオスだったおかげで、いろんな人とお話できてよかったです😊
あと、Matzが歩いていたので写真を撮ってもらってサインを頂きました…!!!

人と参加すると、もっと楽しい

今回お世話になりました、エモリハウス最高でした!🎉

エモリハウスはRailsGirlsのオーガナイザーをされているエモリさんがゲストハウスを予約してくださって、そこに女子だけで泊まろう!という企画。元々予定していた10人の宿はあっという間に埋まって、17人もの大所帯。
東京以外にも長野や京都、名古屋、愛媛から参加されている方とお話できたり、飲みに繰り出したり(そこでフーリエ変換の話で超盛り上がったり)、ハウスでお酒を囲んだり。
もつ鍋行ったり(参加できなかったけど)、集団登校したり(参加できなかったけど)、温泉行ったり(参加できなかったけど)。 女子だけでわいきゃい楽しんだり真面目な話したり。

もとはリモートで顔あわせしただけで半分以上が初めてお会いした方だったんですが、最終日には、旅立つみんなを毎回見送る仲の良さでした(みんな旅立つんですが)。

普段参加させて頂いているRubyコミュニティの人たちとも、旅先だから余計に仲良くなったり。

ライブやフェスと同じで、一人で参加しても楽しいけど、友達と参加すると、もっと楽しい。と思いました。

来年までに英語できるようになる

Matzが日本人でコミッターの方にも日本人が多く、日本開催ということもあり、英語が話せないと何もできない、ということはありません。
また今回は4トラックあったうち、1トラックは日本人が日本語で話す、という部屋になっていたので、そういった意味でも困ることはありません。
ただ、どうしても聞きたいセッションが英語だったり、海外から来た人とコミュニケーションを取りたいと思った時に、必要になるのが英語。

4月末にOSS-Gateという、OSS活動を始めてみよう!というワークショップに参加させていただいた時もすごく実感したのですが、OSS活動をする時って共通言語は英語なので、勉強しよう、という意思を固くしました。

なお、英語ができる方に聞いてみました。
わたし「英語って、どうやって勉強したらよいですか?」
某さん「シャドーイングがいいですよ。話せないものは聞けない、話せるものは聞けるので」
某さん「英会話って、単語単語や文節で理解するんじゃなくて、数秒で発される文章を固まりで理解するので、シャドーイングおすすめです」
某さん「ただ、英語力が足りないとやっぱり理解できないので、Writing&Readingから始めるのがぼくはおすすめです😊」
わたし「最近Flutterやってて英語の文章に触れる機会は増えたんですが…」
某さん「いいと思いますよ!必要って強いので!」
某さん「ちなみに僕は高校生の時にラジオ英会話やってました。今も努力していないとは言いませんが、毎日15分集中して練習したあれが今の基礎になってます」

最近はスマホに英会話のアプリを入れて、毎朝15分学習をしています。

来年に向けて

閉会式で、来年の開催場所が発表されました。場所は、まつもと!!!

RailsGirlsNaganoのオーガナイザーこばちえさんが松本案内日記を公開してくださっています。
今年は前日入りして大濠公園辺りでまったり散歩したのがすごく楽しかったので、来年も前日入りしようと目論見中。
来年の0日目計画がはかどるエントリーです😆


今回すごく感じたのは、「ソフトウェアは人が作っている」ということ。
だから、その人を知っていた方が、もっと楽しめる、ということ。
コミッターの方々とせっかくお会いしてお話する機会がたくさんあったのに、感謝を伝えるしかできなかったので、もっと人のことを知ろうと思いました。

繰り返しになるんですが、セッションは難しいので、わからないことだらけでした。

ただ、話をしている方々、みんなすごく楽しそうなんですよね。
「俺の書いたコードすごいでしょ!見て!」みたいな感じ。

それが格好よくて強烈に憧れたし、自分もああなりたい、と思いました。
ので、コード書いてゆきます!

RubyKaigi2019タイムテーブル徹底解説聞きかじりメモ

先日RejectKaigiに行って参りました!

pixiv.connpass.com

目玉企画として目前に迫ったRubyKaigi、オーガナイザーの松田明さんと高橋会長によるRubyKaigi2019タイムテーブル徹底解説があったので、できる限りメモしたものを書かせていただきます。
メモできなかったところがたくさんあるのでつっこみ歓迎です🙇
(大変恐縮ですが、敬称略です)

ざっくりTrack説明

  • TrackA: Rubyの処理(言語系)
  • TrackD: 日本語でその他の話
  • TrackB&C: 国際色豊かなその他いろいろな話

一枠凡例


10:00-11:10

  • Yukihiro "Matz" Matsumoto: 今回こそはテックな話
  • 登壇者名: コメント
    • 上から順に、TrackA/B/C/Dの順

Apr.18 - Days.1

09:00-10:00

受付


10:00-11:10

  • Yukihiro "Matz" Matsumoto: 今回こそはテックな話

11:20-12:00

  • Matz & the Ruby Core Team: Ruby開発チームからの正式なアナウンス!目玉のひとつ。

12:00-13:30

ランチタイム


13:30-14:10

  • Takashi Kokubun: Railsアプリがどうなるか(Railsがはやくなった!なはず)
  • Maciej Mensfeld: RubyGems.orgが乗っ取られた話が記憶に新しいなか、セキュリティの便利なツール作ってきましたみたいな話!
    「予言してたみたいですよね」
  • ITOYANAGI Sakura: Pure Rubyのなにか…(メモしきれずorz)
  • ota42y: Swaggerの次のバージョンであるOpenAPI3のお話

15:00-15:40

お昼休憩


14:20-15:00

  • Koichi Sasada: VMのなかのほうをRubyですこしだけ書くといろいろいい話
  • Nate Berkopec: マルチスレッドとかProcessとかチューニングとかの理屈(セオリーと実例)
  • Giovanni Sakti: コンテナマネージャを自作した話
  • joker1007: Rubyの黒魔術を使ってRubyを限界まで拡張するお話

15:40-16:20

  • Yusuke Endoh: Rubyの型付けの話、型をつけるためにRubyをごにょごにょする
  • Alexander Ivanov: Rubyなのに型安全なトランスパイラ的な、タイプスクリプト的な
  • Genadi Samokovarov: WebConsoleの作者、PureRubyでデバッガがかけるぞ!て話
  • Yoh Osaki: Rubyで日本語を自然言語処理(チュートリアルっぽい話から)

16:30-17:10

  • Samuel Williams: 一番新しいRubyコミッター、Fiber周りのコードをすごくはやくした人
  • Matthew Draper: Railsコミッター、bundlerが遅い!ってBundler Alternativeのご提案
  • Paolo Perrotta: メタプロRubyの著者、最近DeepLearning本を書いたのでその話。イントロダクション的な話になると思います
  • Shizuo Fujita: RMagickメモリリークとか直して大分よくなったのでRubyKaigiまでに無事リリースして新しくリリースした話にしたい

17:20-18:00

  • Kazuki Tsujimoto: Ruby2.7にパターンマッチングを入れるぞ、という話。RubyKaigiに間に合うかは別として、2.7では確実に入ります
  • Alex Wood: AWSの中のひと、AWS lamdaのご紹介
  • Shawnee Gao: GraphQLをメタプロを使うとよいよという話、おそらく一番アプリよりな話。Squareで使われてる話
  • Aaron Patterson: 日本語です(笑)

Apr.19 - Days.2

08:30-10:00

朝食


10:00-11:10

  • nagachika: ながちかさんならではな話

11:20-12:00

  • Sam Phippen: RSpecメンテナ、RSpecの中の人
  • Noah Gibbs: (メモ取れずorz)
  • Hitoshi HASUMI: mruby/cのファームウェアをごにょごにょする話、アプリケーションの作り方
  • Kouhei Sutou・Kazuma Furuhashi: CSVが早くなった話ともっと早くなる話

12:00-13:30

ランチタイム🍜


13:30-14:10


14:20-15:00

  • Jake Zimmerman・Paul Tarjan: (メモ取りそこね😇)
  • Michael Grosser: parallelとかparallel_testsとかいろんなGemのメンテナ。CoverageGemのご紹介
  • WorkShop: おやつを食べながらRubyでDataでWorkでShop。15:40までぶち抜き枠。
  • Shugo Maeda: Rubyテキストエディタ

15:00-15:40

おやつたいむ


15:40-16:20

  • Vladimir Makarov: light weightなJITのご紹介
  • Amir Rajan: 任天堂Switchのゲームをmrubyで作った話
  • Alex Rodionov: Seleniumの中の人、どのテストコードとどのプロダクションコードが紐付いているかを覚えておいてリグレッションテストを早く(無駄なく)回す
  • Uchio KONDO: RubyでコンテナでCRIU(Checkpoint and Restore In Userspace)な話

16:30-17:10

  • Emily Stolfo: ElasticSearchの中のひと、ベンチマークやパフォーマンスや現場な話
  • Kevin Menard: ChromeのDevtoolでRubyデバッグできたらよくない?って話
  • Mike McQuaid: homebrewのメンテナ、中の人、バージョン2.0のお披露目
  • Tanaka Akira: RubyDSLといえばrakeだけど、なんでlanguageって特別扱いされてるんだろう?DSLって何?って話

17:20-18:30

LT: いろんな人が話します

Apr.20 - Days.3

08:30-10:00

あさごはん


10:00-11:10

  • CRuby Committers: ぐだぐだっとやります

11:20-12:00

  • Yurie Yamane(team yamanekko)・Masayoshi Takahashi: mrubyのお話
  • Ariel Zelivansky: (ききとりそこね🙈)
  • Justin Searls: RubyConfっぽいすごくいい話
  • Sangyong Sim: Oneshot coverageで実際に使われていないコードを5万行以上もりもり消した話

12:00-13:30

ランチ🍜


13:30-14:10

  • Soutaro Matsumoto: (🙇)
  • Charles Nutter, Thomas E Enebo: Rails6について、唯一のRails
  • Colin Fulton: Apple2でRubyがうごくんですよ!て話(超絶技巧プログラミング的な話がきけるのでは)
  • Go Sueyoshi: いろんなAPIを作ってきたのでその話

14:20-15:00

  • Hiroshi SHIBATA: RubyGemsの未来に対する明るい話
  • Kevin Deisz: プレ実行のパフォーマンス
  • ワークショップ: おやつたいむ、おやつはこの部屋にデプロイされて15時40分までぶち抜きです
  • Sadayuki Furuhashi: MessagePack(世界最速ObjectProtocolPack)Rubyのコードをカリカリにチューニングする話

15:00-15:40

おやつたいむ


15:40-16:20

  • Kenta Murata: pluckが2〜3倍早くなっちゃう話
  • Tung Nguyen: lamdaで動くすごいRailsみたいなものの話
  • Tatsuhiro Ujihisa: Rubyのローカル変数が面白いので40分ローカル変数の話
  • nobu: RubyにTimeにzoneをもたせるように拡張ライブラリとコアの共存について考えたい話

16:30-17:10

  • Urabe, Shyouhei: returnのメモリアロケーションとかの話…???
  • Petr Chalupa: TruffleRuby
  • Colby Swandale: 未来のbundlerの話
  • Naotoshi Seo, Yusaku Hatanaka: GPU使ってDeepLearningが早くなるのをRubyでやる

17:20-18:30

  • Jeremy Evans: rodaとか早いアプリを書くのが好きなひと、Keynoteだけどテックな話

松田さん「告知が足りてないんですけど、パーティは結構追加してるんで、数時間おきにみてください

誰かの参考になると幸いです!

Dartのコンストラクタでインスタンスメソッドを上書いてインスタンス変数を変更したかった(未解決)

タイトル通りなのですが、結論から言うと、現時点ではできないと思います。

きっかけはありがちなこんなコード。

class AwesomeButton extends RaisedButton {
  int _index = 1;
  AwesomeButton()
      : super(
            child: const Text('Awesome!'),
            onPressed: () {
              print(_index);
            });
}

タップする度にインスタンス変数を増やすボタンを作りたいな、と思ったものの、とりあえず通ることを確認しようとprintしただけでコンパイルエラー。

Only static members can be accessed in initializers.

staticにすればビルドは通りますが、インスタンス変数で持ちたい。
調べたところ、次のようなIssueがあがっていました。

Allow read-only access to initializing formals. - GitHub

この問題みんな困っていたみたいで各所からリファレンスされているのですが、タイトル通りread-onlyでコンストラクタ内でインスタンス変数へのアクセスを許可したよ、とのこと。
具体的には次のような変更だったようです。

  • The grammar is unchanged.
  • An initializing formal this.x of a constructor C introduces the name x into a scope that covers C's initializers, but not the body; it is considered to be a final parameter.
  • The semantics of such an initializing formal access is that it accesses the parameter, not the field. This matters because the initializing formal may be captured by a function expression, and the field may be updated.
  • 文法は変更なし。
  • this.xの形でコンストラクタに渡せるようにはしたけど、コンストラクタのボディでは渡せないよ。
    (渡す値はfinalのパラメータを渡す想定だよ)
  • こういうinitializeする時にアクセスするのってfieldじゃなくてparameterなはずだよね。
    functionを渡してfieldを上書けるようにしちゃう問題を起こすかもだからこうしてるよ。
    ※ 超意訳かつふんわり理解なので、間違っていたらご指摘ください。

つまり、こういうことはできるようになりました。

class AwesomeButton extends RaisedButton {
  int _index = 1;
  // 引数にthis.xの形で値を渡せる
  AwesomeButton(this._index)
      : super(
            child: Text('Awesome!'),
            onPressed: () {
              print(_index);
            });
}
class C1 {
  final int x;
  // 単独でもセットできるし
  C1(this.x);
}

class C2 {
  final int x;
  final int y;
  // コロンの後でも代入できるし
  C2(this.x) : y = x + 1;
}

class C3 {
  int x;
  // ボディでも渡せる
  C3(this.x) { x = 100; }
}

class C41 {
  int y;
  int z () {
    return y;
  }
  C41(this.y);
}

class C42 extends C41{
  int x;
  int y;
  // スーパークラスのコンストラクタにも渡せる
  C42(this.x, this.y) : super(y){
    x = 100;
  }
}

けれども、スーパークラスのコンストラクタにファンクションを渡すとエラーになる

class C51 {
  int y;
  int z() {
    return y;
  }

  C51(this.y, this.z);
}

class C52 extends C51 {
  int x;
  int y;
  C52(this.x, this.y)
      : super(y, () { print('hello'); });
}

// => 'z' isn't a field in the enclosing class.エラー

RaisedButtonのサブクラスみたいにプロパティとして渡す場合は上書けるけど、「setterが無い」と怒られる。

class AwesomeButton extends RaisedButton {
  int _index;

  AwesomeButton(this._index)
      : super(
            child: Text('Awesome!'),
            onPressed: () { _index = 10; });
}
// => Setter not found: '_index'.

setterを作ってもだめ。

class AwesomeButton extends RaisedButton {
  int _index;
  void get index => _index;
  void set index(suuji) => _index = suuji;
  AwesomeButton(this._index)
      : super(
            child: Text('Awesome!'),
            onPressed: () { index(10); });
}

// => Only static members can be accessed in initializers.dart(implicit_this_reference_in_initializer)
// => The expression here has a type of 'void', and therefore cannot be used.
// (※ setterはvoidである必要がある)

というわけで調査した結果、ほとほとつかれはてて「Flutterはコンストラクタでインスタンス変数を上書きするようインスタンスメソッドを定義できない」という結論になったのでした。
冒頭で引用した通りの思想なのだと思いますが、ほんとうにつかれました。。。。。

analysis_options.yamlをカスタマイズする(3)

analysis_options.yamlのカスタマイズ、3回め。
今日はerrorsについてです。

公式ドキュメントはこちら

さて、analysis_options.yamlが面白いなと思うのは、チェック基準を厳しくもできるし、ゆるくもできるところ。

analyzer:
  errors:
    missing_required_param: warning
    missing_return: warning
    todo: ignore
    sdk_version_async_exported_from_core: ignore

errorsに続けてオプションを記述し、[warning, error, info, ignore]を記述することで、チェックツールの挙動をカスタマイズすることができます。

info
An informational message that doesn’t cause analysis to fail. Example: todo
(ざっくり訳) コンパイルエラーを起こさないインフォのこと。 ex. todo

warning
A warning that doesn’t cause analysis to fail unless the analyzer is configured to treat warnings as errors. Example: analysis_option_deprecated
(ざっくり訳)アナライザーがワーニングをエラーと捉えなければコンパイルエラーを起こさないワーニングのこと。ex. analysis_option_deprecated

error
An error that causes analysis to fail. Example: invalid_assignment
(ざっくり訳)コンパイルエラーを起こすエラーのこと。ex. invalid_assignment

たとえばmissing_required_paramsrequiredのプロパティを設定しないとウィジェット(画面)の描画時にエラーが発生するけど、コンパイルエラーは起こさない(warning状態)。

// Paddingはpaddingプロパティが必須(required)
// 指定しないとコンパイルは通るけど、描画しようとすると例外が発生する
Padding hoge() {
    return Padding(
      child: Text('Hello, World!'),
    );
  }

これをerrorに指定すると、同じ記述でもアナライザーがエラーを表示してくれます。 (エラーは表示されるだけで、コンパイルは通ります)。

analyzer:
  errors:
    // errorを指定するとエラーとして表示されるようになる
    missing_required_param: error

missing_returnreturnを記述していない場合の挙動。
ウィジェット生成のファンクションはreturnを指定しなくても動くのですが、明示的にわたすほうが好ましいようです。

ということで今日はここまで、また明日続きからやっていきます!

analysis_options.yamlをカスタマイズする(2)

昨日の続きで、analysis_options.yamlの項目たちの説明です。 昨日の復習も含めて書いてゆきます。

analyzer:
  strong-mode:
    implicit-casts: false
    implicit-dynamic: false

動的型付け・型のキャストを許可する項目です。どちらもデフォルトはtrue
動的型付け・型のキャストを禁止したい時のみfalseで指定します。

implicit-casts: false

暗黙的な型変換を許可しない設定。

Object o = 'hoge';
String s = o;

ObjectクラスのインスタンスoStringクラスの変数sに代入しようとすると文字列をStringに入れるのでコンパイルは通るのですが、implicit-casts: falseに反して暗黙的な型変換が行われるのでアラートが発生するようになります。
アラートを避けるには、型変換が行われないよう変数の型を揃える必要があります。

String o = 'hoge';
String s = o;

implicit-dynamic: false

動的型付けを許可しない設定。
castsは型変換禁止なので、ObjectStringとなると静的型付だけどアラートが出るし、dynamicは動的型付け禁止なので、型をつけていないとアラートが出るという感じですね。

抜けがちなのは配列やオブジェクトの時かなと思いました。それぞれ要素の型を指定する必要があります。

// 1. hogeはListだけど、Listの中身を指定していないのでアラートが出る
final List hoge = ['hoge'];

// 1. Listの中身をジェネリクスで指定するとアラートが解消される
final List<String> hoge = ['hoge'];

// 2. MapのListだと指定しているけど、Mapの中身を指定していないので
// Missing type arguments for map literalアラートが出る
final List<Map> hoge = [{'fuga': 'piyo'}];

// 2. Mapの要素の型を再帰的に指定するとアラートが解消される
final List<Map<String, String>> hoge = [{'fuga': 'piyo'}];

// 3. 明示的にvarを指定するとアラートは出ない
var hoge = ['hoge'];

// ちなみにそもそもvarと型は共存できない(analysis_options.yamlによらず)
// これはコンパイルエラー
// "Variables can't be declared using both 'var' and a type name."
// var List hoge = ['hoge'];

関数呼び出しの際も、返り値のクラスを指定する必要があるようです。
たとえば、showDialogメソッドで返ってくるのはAwesomeDialogインスタンスなので指定します。

// アラートが出る
showDialog(
  context: context,
  builder: (_) {
    return AwesomeDialog();
  }
);

// 返り値のクラスを指定するとアラートが出なくなる
showDialog<AwesomeDialog>(
  context: context,
  builder: (_) {
    return AwesomeDialog();
  }
);

以上、動的型付け禁止の項目でした!