テックアカデミー第11回メンタリング:いろいろ解決!JSONレスポンスがnull、インスタンスを持ちまわる方法、formで入力しない項目もPOSTしたい

2018年4月9日

今日はテックアカデミーの講師とのビデオ通話面談で様々な疑問を解消できたので、それをまとめています。

皆さんに役立つ記事にはなってないかもしれませんが、トピックは豊富です!

自分が後から見直したり、同じよう悩みを抱えているプログラミング初心者の参考になるコードを探す時に使えれば幸いです。

レスポンスのJSON形式にカラがあってエラーになる問題

Yahoo MAP APIにリクエストを投げて、JSON形式でデータを取ってきたら、中にNULLの項目があるときがあって、それを扱おうとするとエラーが出てしまう。

これをどうやって回避すればよいかを聞きました。

まず私がやってるのはこのようにしてJSON形式のレスポンスを取得します。

yquery_enc = URI.encode yquery      #日本語を%のUTF-8の形にエンコード
uri = URI.parse(yquery_enc)
json = Net::HTTP.get(uri)
@results = JSON.parse(json)         #配列形式に変換
@features = @results['Feature']     #Feature配下の配列のみとりだす。これで店舗一覧がとれる。

レスポンスコードの一覧はYahooのページを確認。
レスポンスコード

最後の文で@features配列から1個1個とりだしています。Yahoo地図APIから取得した店舗情報がJSON形式からハッシュに変換して入っています。

ここからshopのインスタンスを生成していく実装です。

@features.each_with_index do |feature,i|
  @shops[i] = Shop.new(
    name: feature['Name'],
    y_id: feature['Id'], 
    y_gid: feature['Gid'], 
    y_uid: uid,             #feature['Property']['Uid'], 
    y_ido: ido,             #feature['Geometry']['Coordinates'].split(",")[1], 
    y_keido: keido,         #feature['Geometry']['Coordinates'].split(",")[0], 
    y_address: address,     #feature['Property']['Address'], 
    y_moyorieki: eki,       #feature['Property']['Station'][0]['Railway'] + " " + feature['Property']['Station'][0]['Name']+"駅 徒歩" + feature['Property']['Station'][0]['Time'] + "分"
    y_leadimage: image_url  #feature['Property']['LeadImage']
  )
end

こんな風にしてやりました。ところがfeature[‘Property’]やfeature[‘Property’][‘Station’]がカラだと、Railsでエラーが出てしまいます。

エラーを回避するにはどうすればよいか検討しました。

私が導き出した答えは

feature[‘Property’].present?
で、カラかどうかをチェックしてカラだったら
uid = nil
カラじゃなかったら値をそのまま入れる
uid = feature[‘Property’][‘Uid’]

ということをやりました。問題は、これ全部の項目やんの?(; ・`ω・´)

てことなんです。

なのでいま以下のようなプログラミングコードを書いています。

@features.each_with_index do |feature,i| #@features配列から1個1個とりだしてshopのインスタンスを生成していく

  unless feature['Property'].present?  #propertyがカラなら
    uid = nil
    address = nil
    image_url = nil
  else                                  #propertyがあるなら
    uid = feature['Property']['Uid']
    address = feature['Property']['Address']
    image_url = feature['Property']['LeadImage']
    
    #なかのstationがあるかチェック        
    unless feature['Property']['Station'][0].present? #nil.['Railway']とやるとエラーでるからチェックしないといけない #stationがカラなら
    	eki = nil
    else  #stationがあるなら
    	eki = feature['Property']['Station'][0]['Railway'] + " " + feature['Property']['Station'][0]['Name']+"駅 徒歩" + feature['Property']['Station'][0]['Time'] + "分"
    end
  end
  
  unless feature['Geometry'].present? #Geometryがカラなら
    ido = nil
    keido = nil
  else                                #Geometryがあるなら
    unless feature['Geometry']['Coordinates'].present? #Coordinatesがカラなら
      ido = nil
      keido = nil
    else
      ido = feature['Geometry']['Coordinates'].split(",")[1]
      keido = feature['Geometry']['Coordinates'].split(",")[0]
    end
  end
&&というのが使えますよ。これは値があればすすむ、ということができるんです。

例えば

hash = { address: { city: :sapporo } } こういうのがあったときに

データはhash[:address][:city] でsapporoがとれる

そういうときは
hash[:address] && hash[:address][:city]

とやれば、adoressがないときは、後ろを実行しない。これを活用できる。

あとはdigメソッド
hash.dig(:address, :city)

これだと、キーがnilであってもエラーがおきません。

とは言え今回はRailsでもそんなに手短かにもならない感じですね。

なるほど。理解しました。新しい書き方を覚えなきゃいけないので、取り敢えずはunless文をずらずら書いて動いてるので、このまま行ってみます。

indexアクションで生成したインスタンスを持ち回りたい

shop#indexアクション → shops/index.html.erb → shop#newアクション へインスタンスを持っていきたい

けど、Railsでもこれが出来ないので解決策を探しています。

indexアクションで作った変数やインスタンスは@を付けてあげればVIEW(index.html.erb)に持っていけます。

持っていったインスタンスは1個1個展開して、

newアクションへのクエリ文でパラメータを全部URLの後ろにつなげることをやってみました。

<%= link_to 'ニューに飛ぶ', new_shop_path(shop, name: shop.name, address: shop.address), class:  %>

これをするとURLが
http://sample/?name=デニーズ新宿店&address=東京都新宿区ほげほげほげほげ1-2-3&eki=都営新宿線/JR線/地下鉄副都心線 新宿駅徒歩3分・・・・

みたいになります。

#newアクション側で送られてきた上記のURLからパラメータを取得します。そのコードは以下のとおり。

    hash = {}
    request.query_parameters.each do |key, value|
    hash[key] = value.to_s
    puts hash[key]
これで取り出しができました。

URLの文字数は2048文字までだしオーバーする可能性もあります。それに見た目もスマートじゃないんですけど。Railsなら簡単に出来る方法ってないでしょうか?

実装方法は他にもありますね。

クッキーにセッション情報をうめこんでいることはご存知でしょうか。これを活用できます。

クッキーに店舗情報を入れて持ち回ればよいですね。

他にはサーバー側にセッション情報をもたせることもできます。
キーだけクライアントがもってて、サーバー側がキーを元に店舗情報を復元するようなイメージもできますね。

ということでした。セッションを学び直すのは大変なので、取り敢えず今できてる方法で進めてみることにしました。

コントローラーで作ったハッシュがビュー側に来ない問題

先程のコントローラー側でハッシュを作っています。

    @hash = {}
    request.query_parameters.each do |key, value|
      @hash[key] = value.to_s
      puts @hash[key]
    end

@をつければビュー側で展開できるので以下のようにしていますが、なぜか取り出しができません。

<p><%= @hash[:name] %></p>

これで取り出しができると思ったのですが。。

この場合はコントローラーで入れてるキーがシンボルじゃなくて文字列になっちゃってるのが原因です。

ビュー側では、@hash[‘name’]で表示ができますよ。

全項目修正するのが大変であれば以下のメソッドをコントローラーで使ってみましょう。

@hash.symbolize_keys!

これでハッシュのKEYをシンボルに変更してくれます。便利なメソッドですね。

フォームで入力しない項目もcreateアクションに送信したい

店舗への口コミ投稿フォームでは、

<%= form_for @kuchikomi |f| %>

このように@kuchikomiでモデルを渡して口コミ情報を記入してもらっています。
ところがフォームで渡したいのは口コミ情報以外にもあって、それをどうやって渡せばよいか探しています。

それはhiddenタグで可能です。

<% @hash.each do |key, value| %>
<%= hidden_field_tag "shop[#{key}]", value %>
<% end %>

こんな形ですね。

これで
indexアクション→index.html.erb→newアクション→new.html.erb→createアクション

とデータを持ち回る事ができるようになりました。

さらに発展して以下のようにできました。これはアクション側で@shopインスタンスを生成して、それをビュー側でhiddenタグに入れるやり方です。

<% @shop.attributes.each do |key, value| %>
<%= hidden_field_tag "shop[#{key}]", value %>
<% end %>

index.html.erb→newアクションのときは、GETメソッドでURLの後ろにクエリをつなげて。
new.html.erb→createアクションのときはPOSTメソッド(formタグ)の中にhiddenタグで埋めてあげて。

なんだか自分が書くとややこしくなってる気がするけど。。

今日は非常に密度の濃い30分となりました。かなり前進した気がします!