ひよっこエンジニアの雑多な日記

なんとかWeb系のエンジニアをやっています。

jQueryでアコーディオンパネルを作る

最近javascriptを使えないと世の中的にやばいんじゃないかと思いjQueryを勉強し始めました…。
勉強してる中でアコーディオンパネルを作ったわけなんですが、想像以上に簡単に実装できちゃう感じを知り若干感動したのでブログ化です。
(これで感動しているあたり素人感丸出しなのは内緒。)
今更こんなん記事にします?っていう指摘は言わないであげてください。笑

アコーディオンパネルってなんや

アコーディオンパネルはクリックするとクリックした項目の下に文字なんかがスライドで出てきて、もう一回クリックするとスライドしてもとに戻るやつです。
説明下手すぎてイメージつかないですね。
つまりこんな感じのやつです。

初期表示
f:id:kimuraysp:20160211182428p:plain

でこれの一番上の項目をクリックすると
f:id:kimuraysp:20160211182500p:plain

こんな感じでクリックするとヌルッと出てくるやつです。
もう一回同じ項目をクリックすると初期表示の状態に戻ります

とりあえず作ってみよう

ということでまずはHTMLとCSSを作ります。

HTML
<ul id="accordion mdl-list">
  <li class="accordion-item mdl-list__item">
     <h3 class="question">職業はなんですか?</h3>
     <span>+</span>
     <div class="answer">
        <p>システムエンジニアです。いわゆるSEです。</p>
     </div>
  </li>
  <li class="accordion-item">
     <h3 class="question mdl-list__item">なんのスポーツが好きですか?</h3>
     <span>+</span>
     <div class="answer">
	<p>野球</p>
     </div>
  </li>
  <li class="accordion-item mdl-list__item">
     <h3 class="question">携帯は何使ってますか?</h3>
     <span>+</span>
     <div class="answer">
        <p>iPhone</p>
     </div>
  </li>
</ul>

初期に表示されている箇所がh3要素、出たり隠れたりする箇所が<div class="answer">~</div>の部分になります。

CSS
ul {
  list-style: none;
}

.accordion-item {
  position: relative;
  width: 400px;
  border-bottom:1px solid #ccc;
  cursor: pointer;
  margin-top: 5px;
}
.accordion-item:hover {
  background-color: #E6E6E6;
}

.question {
  font-size: 18px;
  margin: 0;
  padding: 0;
}

.accordion-item span {
  position: absolute;
  top: 5px;
  right: 5px;
  color: #B3B3B3;
  font-size: 20px;
}

.answer {
  display: none;
  font-size: 12px;
}

初期表示の状態では出たり隠れたりする部分をdisplay: none;で画面に表示されないようにしておきます。

javascriptを書くッッ

さてアコーディオンパネルを実現するためのjavascriptを書きます。
とはいってもjQueryを使うとすごくさっくりした記述で処理が書けちゃいます!

$(function() {
  $('.accordion-item').click(function(){
        
    //出隠れする部分を変数に格納しておく
    var $answer = $(this).children('.answer');

    //hasClassメソッドで出隠れする部分にopenが存在するか確認
    if ($answer.hasClass('open')) {

      //出隠れ部分が出ている場合の処理

      //&ltdiv class="answer open"&gtからopenを消す
      $answer.removeClass('open');

      //出ている部分をスライドアップして隠す
      $answer.slideUp();

      //横っちょにある開閉ボタンを+に変える
      $(this).children('span').text('+');
    } else {

      //出隠れ部分が隠れている場合の処理
      
      //&ltdiv class="answer"&gtにopenを追加する
      $answer.addClass('open');

      //出す部分をスライドダウンして表示させる
      $answer.slideDown();

      //横っちょにある開閉ボタンを-に変える
      $(this).children('span').text('-');
    }
  });
});

<div class="answer">にopenを追加することで開いたり、隠したりする感じ。
openを追加することでCSSに記載しているdisplay: none;が無効になったり、有効になったりするイメージかしら。

とはいえたった10行前後で良く見るあのアコーディオンパネルさんを実現できるのはちょっと感動。
Ruby on Railsを極めつつ、javascriptも極めていきたいっすなあ。
とはRailsさんはcoffeescriptだからまた勉強が必要やが…。

そのうちcoffeescript備忘録つくろ

Railsで関連モデルを同時に更新したい(has_oneで関連させていたモデルをbuild_associationで安直に生成しようとしたら痛い目をみた話)

Railsで関連モデルを同時に更新するようなコードを書こうとして、思いの外ハマって抜けられなくなったので備忘録。

関連モデルを同時に更新したい状況

例えばメールアドレスやパスワードのような認証情報と、生年月日や趣味のようなプロフィール情報を分けて管理したい場合など結構ありがちだと思います。 以下のような関連を持つモデルのようなイメージ。

f:id:kimuraysp:20160207000912p:plain

とりあえず書いてみる

ひとまず自分の思うがままにコードを書いていく。

まずモデルから。

app/models/user.rb

class User < ActiveRecord::Base  
  has_one :profile, dependent: :destroy  
  accepts_nested_attributes_for :profile  
end  

app/models/profile.rb

class Profile < ActiveRecord::Base
  belongs_to :user
end  

親モデルのUserにaccepts_nested_attributes_forを設定することで親モデルから子モデルを作成したり、保存したりできるようにする。

次にコントローラ。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  def index
    @users = User.all
  end

  def show
  end

  def new
    @user = User.new
  end

  def edit
    @user.build_profile
  end

  def create
    @user = User.new(user_params)
    @user.save
    redirect_to :users
  end

  def update
    @user.update(user_params)
    redirect_to :users 
  end

  def destroy
    @user.destroy
    redirect_to :users
  end

  private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).permit(:email, :password, profile_attributes: [:birthday, :hobby])
    end
end

viewでfields_forを使うのでprofile_attributesをストロングパラメータに記入しておく。
(fields_for[引数に与えた名前]_attributesというname属性になるためらしい)
仕様としてプロフィール情報を入れるのはユーザ情報編集時のみとするため、登録しているUserモデルからProfileモデルを生成できるようにedit@user.build_profileと何気なく記入。
親から子を生成するときはとりあえずbuildっしょとかいう素人考えでbuild_profileを記入したが 、後にどツボへの道へと誘うことになるとはこの段階では知る由もなかった…。

とりあえず次にビュー。

app/views/_form.html.erb

<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :email %><br>
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %><br>
    <%= f.password_field :password %>
  </div>
  
  <% unless current_page?(new_user_path) %>
    <%= f.fields_for :profile, @user.profile do |pf| %>
      <div class="field">
        <%= pf.label :birthday %><br>
        <%= pf.date_field :birthday %>
      </div>
      <div class="field">
        <%= pf.label :hobby %><br>
        <%= pf.text_field :hobby %>
      </div>
    <% end %>
  <% end %>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

プロフィール情報はnewのタイミングでは表示させないようにcurrent_page?を設定。
fields_forのタグを<%= %>ではなく<% %>で記入してプロフィール情報入力欄出ない問題にも軽くハマっているのは内緒の話。
これでとりあえず一通り完成!

てことで動作確認

ユーザを登録

f:id:kimuraysp:20160207111430p:plain

次にプロフィール情報を入力するために編集

f:id:kimuraysp:20160207111542p:plain f:id:kimuraysp:20160207111643p:plain
いい感じにプロフィール情報を入力できるようになっている。
とりあえずプロフィール情報を入力。

登録できたか確認

f:id:kimuraysp:20160207111651p:plain
いい感じに登録できている。

しかし、問題が起きる…

登録までできて、「いやー、ひと仕事終わったよ」と清々しい気分でいましたが、再び編集画面を表示した際に違和感を覚える。
f:id:kimuraysp:20160207111542p:plain
あれ…?プロフィール情報が表示されてなくね…?

いやいや、まさかそんなはずは…と思い、再びshowしてみるとProfileモデルなんて存在してねーけど…と怒られる。
f:id:kimuraysp:20160207113010p:plain

DBも確認してみたが、profilesテーブルがまっさらになっている…

>> Profile.all
  Profile Load (0.5ms)  SELECT "profiles".* FROM "profiles"
=> #<ActiveRecord::Relation []>

さっきまで存在していたプロフィール情報がどこか遠い国に旅立ってしまっている…

登録し直したり、デバッグしてみたりで原因を探ること小一時間。
問題点があらわになる。
@user.build_profileを実行した後にレコードが消えている…!!!!!

サーバログを見てみると

Started GET "/users/8/edit" for ::1 at 2016-02-07 12:12:38 +0900
Processing by UsersController#edit as HTML
  Parameters: {"id"=>"8"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 8]]
  Profile Load (0.2ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."user_id" = $1 LIMIT 1  [["user_id", 8]]
   (0.1ms)  BEGIN
  SQL (0.2ms)  DELETE FROM "profiles" WHERE "profiles"."id" = $1  [["id", 7]]
   (2.0ms)  COMMIT
  Rendered users/_form.html.erb (3.0ms)
  Rendered users/edit.html.erb within layouts/application (4.2ms)
Completed 200 OK in 38ms (Views: 20.1ms | ActiveRecord: 2.7ms)

なんかDELETEが走っちゃってますけど!!!!

どうもbuild_profileでProfileモデルを生成の際は一旦存在するレコードを消してから生成みたいなことが起きているっぽい。

ということでbuild_profileを使わないように修正。

def edit
    @user.profile = Profile.new if @user.profile.blank?
end

てな感じで無事プロフィール情報を登録してから編集画面にいっても情報が消えなくなりました。

とりあえず関連モデルを生成するならbuildじゃね?という安易な考えを悔い改めようと思いましたまる

vagrant環境でrake db:migrateが失敗する

vagrant環境でRailsでrake db:migrateを実行する際にエラーが出て時間を食ってしまったので備忘録
使用しているDBMSpostgresql

$ rake db:migrate  
rake aborted!
ActiveRecord::NoDatabaseError: FATAL:  role "vagrant" does not exist
PG::ConnectionBad: FATAL:  role "vagrant" does not exist

どうやらvagrantなんてロールはいないよとのこと。。。
なのでvagrantロールを作成してやることに。

$ sudo su - postgres  
$ psql  
$ create role vagrant with createdb login  
  
$ \du  
Role name | Attributes  | Member of   
-----------+-------------+-----------  
postgres  | Superuser   | {}
       : Create role   
         Create DB    
vagrant   | Create DB   | {}

とりあえずDB作れる権限だけ与えて、vagrantロールを作成 postgresqlからexitして再びmigrate

$ rake db:migrate  
rake aborted!</span>  
ActiveRecord::NoDatabaseError: FATAL:  database "app_development" does not exist  
PG::ConnectionBad: FATAL:  database "app_development" does not exist

今度はapp_developmentなんてDBは存在しないよとのこと。。。
今度はDBを作成してやることに。

$ sudo su - postgres  
$ psql  
$ create database app_development  
CREATE DATABASE

これでDBも作成できたので、またexitしてmigrate

$ rake db:migrate  
== 20151103022733 CreateProjects: migrating ===================================
  -- create_table(:projects)  
  -> 0.0277s
== 20151103022733 CreateProjects: migrated (0.0278s) ==========================

無事migrateができた。
なんかすごい初歩的なことに時間を取られた。。。
前はこんな設定せずにサクッとrailsが使えた記憶があったせいか、なおさら時間がかかった。。。

今度はherokuなんかにあげるときに問題が起きそうな予感。。。
とりあえずmigrateできたのでガシガシアプリ作りに励みます。

ブログはじめました

はじめまして。KMRです。

社会人2年目のSIerです。 現在、Web系で働くことを夢見て、絶賛Ruby on Railsを勉強中です。

ブログを始めるに至った経緯としては

  • 勉強していることの備忘録を作りたい
  • アウトプットすることでより自分の理解を深めたい
  • 情報発信力が求められる世の中なので、その訓練に
  • 情報を発信していくことで自分の中の何かを変えたい

的な経緯があります。

今後、記事を書いていく内容としては

  • 勉強の備忘録
  • 本やコラムを見ての感想
  • 個人的な思いとか、日常風景とか。。。

みたいな感じで書いていきたいと思います。

まずは情報を発信していく訓練をしていきたい所存です。 これからよろしくお願いします!