LoginSignup
2
3

More than 5 years have passed since last update.

railsとVue.jsで電話帳アプリ作成

Posted at

初めに

RailsとVue.jsを使って電話帳アプリを作成してみました。

こんな感じです。

vue.gif

最初はVue.jsとRailsのapiを使って作成しようとしたけどCORSとかなんかのせいでうまくできなかった。

具体的にはlocalhost:8000で起動しているVue.jsからlocalhost:3000にaxiosを使ってアクセスしようとしても出来なかった。

なのでRailsにVue.jsを入れて?作った。

最初の設定

とりあえず

rails new vue-app

yarnをインストールしてない場合はインストール

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn

gemを追加していく

デザインにbootstrapダミーデータ作成にfakerそしてvuejsを使うために必要なgemを加える。

gem 'webpacker', github: 'rails/webpacker'
gem 'faker'
gem 'bootstrap'
gem 'jquery-rails'
gem 'popper_js'
gem 'tether-rails'

そしてbundle install

そのあとに

bin/rails webpacker:install
rails webpacker:install:vue

でVueを使えるようにして。

assets/javascriptのapplication.jsを

//= require jquery3
//= require jquery_ujs
//= require popper
//= require tether
//= bootstrap
//= require activestorage
//= require turbolinks
//= require_tree .

としてbootstrapを使えるようにする。

userモデルを作成してそのあとダミーデータを作成する

rails g model user name:string phone:string address:string
rails db:migrate
rails c
100.times { User.create(name: Faker::Name.first_name, phone: Faker::PhoneNumber.cell_phone, address: Faker::Address.full_address

これで100件のデータが作成できる。

コントローラーの作成をする

rails g controller users index

vueを表示するためにlayouts/application.html.erbを

<%= javascript_pack_tag 'hello_vue' %>

を<%= yield %>の下に追加する。

fontawesomeを使うので

head内に

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css">

を追加しておく。

これでとりあえずアプリの土台はOK

controller作成

次にcontrollerを作成していく。

def index
end 

def getuser
  @users = User.all
  render :json => @users 
end 

def create
  @user = User.new(user_params)
  if @user.save 
    render :index
  end 
end 

private 

def user_params 
  params.fetch(:user, {}).permit(
      :name,:phone,:address
    )
end 

これでcontrollerはOK

getuserに関してはほかにコントローラー作ってそこに書いたほうが良いのかも…

indexにアクセスして電話帳を表示してvue内でgetuserにアクセスして@usersのjsonデータを取得して反映させる感じになる。

createに関してはrender :indexはいらない気がするけどとりあえず書いてある。

routes.rbを編集

get 'users/index'
get 'users/getuser'
post 'users/create'

みたいにすればとりあえずOK

Vueを作っていく

Vueに関するファイルはapp/javascriptの中にある

app.vueが一番もとになるファイル(多分)

packsの中にhello_vue.jsがありこれが

<%= javascript_pack_tag 'hello_vue' %>

これで読み込まれている。

hello_vue.jsの中に

import App from '../app.vue'

でapp.vueを読み込んで

render:h => h(App)

でapp.vueを表示している(多分)。

あんまり意味ないけどjavacriptいかに

Phone.vueファイルを作って

hello_vue.jsに

import Phone from '../phone.vue'

をして

render: h => h(Phone)

と変更する

これでPhone.vueを読み込んでくれる。

Phone.vueのtemplate

phone.vueのtemplateにhtmlを書いていく

まず最終的なtemplateを張っておくのでとりあえずコピペで動かしたい場合はここをコピぺしてください。

<div class="container">
    <div id="app">
      <h3 class="top-heading">連絡先追加</h3>
      <div class="row">
        <div class="col-md-5 col-left-item">
          お名前
        </div>
        <div class="form-group col-md-7">
          <input class="form-control" type="text" v-model="cword" placeholder="Enter name ...">
        </div>
      </div>
      <div class="row">
        <div class="col-md-5 col-left-item">
          電話番号
        </div>
        <div class="form-group col-md-7">
          <input class="form-control" type="text" v-model="pword" placeholder="Enter phonenumber ...">
        </div>
      </div>
      <div class="row">
        <div class="col-md-5 col-left-item">
          住所
        </div>
        <div class="form-group col-md-7">
          <input class="form-control" type="text" v-model="aword" placeholder="Enter address ...">
        </div>
      </div>
      <div class="submit-btn" v-on:click="createUser">
        <i class="fas fa-arrow-alt-circle-down"></i>
      </div>
      <div class="form-group search-form">
        <input class="form-control" type="text" v-model="keyword" placeholder="Search user ....">
      </div>
      <div class="row">
        <div class="col-md-4">
          <tr class="usersList" v-for="user in filteredUsers" :key="user.id">
            <td class="nameList" v-on:click="showUser(user.name)">{{ user.name }}</td>
          </tr>
        </div>
        <div class="col-md-8 infoWrap">
          <div class="infoBox animated" data-animate="bounceInLeft">
            <p>ユーザー情報</p>
            <div id="userInfo">
              <table>
                <tr>
                  <td>名前</td>
                  <td>{{ result.name }}</td>
                </tr>
                <tr>
                  <td>電話番号</td>
                  <td>{{ result.phone }}</td>
                </tr>
                <tr>
                  <td>住所</td>
                  <td>{{ result.address }}</td>
                </tr>
              </table>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

連絡先追加の部分がこれで

      <div class="col-md-5 col-left-item">
          お名前
        </div>
        <div class="form-group col-md-7">
          <input class="form-control" type="text" v-model="cword" placeholder="Enter name ...">
        </div>
      </div>
      <div class="row">
        <div class="col-md-5 col-left-item">
          電話番号
        </div>
        <div class="form-group col-md-7">
          <input class="form-control" type="text" v-model="pword" placeholder="Enter phonenumber ...">
        </div>
      </div>
      <div class="row">
        <div class="col-md-5 col-left-item">
          住所
        </div>
        <div class="form-group col-md-7">
          <input class="form-control" type="text" v-model="aword" placeholder="Enter address ...">
        </div>
      </div>
      <div class="submit-btn" v-on:click="createUser">
    <i class="fas fa-arrow-alt-circle-down"></i>
      </div>

inputにそれぞれv-modelがある。

例えば名前のinputにv-model="cword"がありこれによって名前のinputの値をvueのscriptの部分でcwordという変数で取得できる。

なので名前、電話番号、住所にcword,pword,awordというv-modelがついているので

script側で

axios.post('/users/create', { user: {name: this.cword, phone: this.pword, address: this.aword } })

でデータを保存できるようになる。

ちなみに

axios.postは第一引数にurlを第二引数に渡すデータを書く。

登録ボタンはv-on:clickで実装できて

      <div class="submit-btn" v-on:click="createUser">
    <i class="fas fa-arrow-alt-circle-down"></i>
      </div>

この場合div class="submit-btn"がクリックされたときにcreateUserというmethodを実行することになります。

createUserはscript側で後で実装します。

検索部分はこれで


      <div class="form-group search-form">
        <input class="form-control" type="text" v-model="keyword" placeholder="Search user ....">
      </div>
      <div class="row">
        <div class="col-md-4">
          <tr class="usersList" v-for="user in filteredUsers" :key="user.id">
            <td class="nameList" v-on:click="showUser(user.name)">{{ user.name }}</td>
          </tr>
        </div>
        <div class="col-md-8 infoWrap">
          <div class="infoBox animated" data-animate="bounceInLeft">
            <p>ユーザー情報</p>
            <div id="userInfo">
              <table>
                <tr>
                  <td>名前</td>
                  <td>{{ result.name }}</td>
                </tr>
                <tr>
                  <td>電話番号</td>
                  <td>{{ result.phone }}</td>
                </tr>
                <tr>
                  <td>住所</td>
                  <td>{{ result.address }}</td>
                </tr>
              </table>
            </div>
          </div>
        </div>

v-model="keyword"のところで検索ワードを取得している

v-for="user in filteredUsers"でkeywordの値にマッチするuserを表示している。

v-forはループ?ができて例えばusersにuser全員が入っているとして

v-for="user in users"とすれば

rubyでいう

users.each do |user|
end 

みたいに使うことができる。

filteredUsersにはkeywordにマッチしたものが入っているのでuser in filterdUsersとすれば検索を実装できる。

filteredUsersは後でscript内のcomputedの中に定義する。

後は詳細データの表示でuser一覧の中から名前をクリックするとそのデータを取得してその値をresultに入れて詳細を表示するようにしている。

それは

          <tr class="usersList" v-for="user in filteredUsers" :key="user.id">
            <td class="nameList" v-on:click="showUser(user.name)">{{ user.name }}</td>
          </tr>

この部分で名前がクリックされたときにshowUserというメソッドをuser.nameを与えて実行している。

showUserではuser一覧からおんなじ名前のuserを検索して見つかったらresultにその値を入れている。

ちなみにこのやり方だと同じ名前のユーザーがいるとだめなので登録の際は同じ名前のユーザーを登録できないようにする必要がある。

これでとりあえずtemplate部分はおわり

phone.vueのscript

こちらに関してもコピペ用に最初に全部を張っておきます

import axios from 'axios'
export default {
  data: function () {
    return {
      cword: '',
      pword: '',
      aword: '',
      keyword: '',
      result: {},
      users: []
    }
  },
  created: function() {
    this.getData();
  },
  methods: {
    getData: function(){
      fetch('/home/users')
      .then(dataWrappedByPromise => dataWrappedByPromise.json())
      .then(data => {
        this.users = data;
      })
    },
    createUser: function() {
      if (!this.cword) return;

      for(var i in this.users) {
        var user = this.users[i];
        if (user.name.toLowerCase() == this.cword.toLowerCase()) {
          console.log("can not save user")
          return;
        }
      }

      axios.post('/home/create', { user: {name: this.cword, phone: this.pword, address: this.aword } }).then((response) => {
        var luser = {name: this.cword, phone: this.pword, address: this.aword };
        this.users.unshift(luser);
        this.cword = '';
      })
    },
    showUser: function(d) {
      this.result = [];
      for(var i in this.users) {
        var user = this.users[i];
        if(user.name === d) {
          this.result = user;
        }
      }
      var animationName = $('.infoBox').data('animate');

      $('.infoBox').addClass(animationName).delay(1000).queue(function(next){
        $('.infoBox').removeClass(animationName);
        next();
      });
    }
  },
  computed: {
    filteredUsers: function() {
      var users = [];

      for(var i in this.users) {
        var user = this.users[i];

        if (user.name.toLowerCase().indexOf(this.keyword) !== -1) {
          users.push(user);
        }
      }
      return users;
    }
  }
}

まずaxiosを使うのでコンソールでアプリのフォルダに移動して

yarn add axios

としてaxiosを使えるようします。

そして

import axios from 'axios'

で読み込みます。

export default {
}

この中にdataなりmethodsなりをかいていきます。

まずv-modelで使うものはdataで定義されていないと使えないので

data: function () {
  return {
      cword: '',
      pword: '',
      aword: '',
      keyword: '',
      result: {},
      users: []
    }
}

と書きます。

usersにかんして空だとおもうかもしれませんが

これに関しては

createdの中でaxiosを使ってデータを取得してusersに入れるので大丈夫です。

createdに関しては知らないけど名前的に多分vueが読み込まれる時?に実行されるんだと思う

なので

created: this.getData();

と書いておきます。

getDataに関してはmethodsで定義します。

methodsとcomputedの違いがわからないですけどfilteredUsersとかはcomputedにかいています。

getDataとかはmethodsに書いています。

今回createUserとshowUserをmethods内にかいてますけど多分computedの中に書いたほうがよさそうです。

とりあえず

methodsの中に

  methods: {
    getData: function(){
      fetch('/home/users')
      .then(dataWrappedByPromise => dataWrappedByPromise.json())
      .then(data => {
        this.users = data;
      })
    },
    createUser: function() {
      if (!this.cword) return;

      for(var i in this.users) {
        var user = this.users[i];
        if (user.name.toLowerCase() == this.cword.toLowerCase()) {
          console.log("can not save user")
          return;
        }
      }

      axios.post('/home/create', { user: {name: this.cword, phone: this.pword, address: this.aword } }).then((response) => {
        var luser = {name: this.cword, phone: this.pword, address: this.aword };
        this.users.unshift(luser);
        this.cword = '';
      })
    },
    showUser: function(d) {
      this.result = [];
      for(var i in this.users) {
        var user = this.users[i];
        if(user.name === d) {
          this.result = user;
        }
      }
      var animationName = $('.infoBox').data('animate');

      $('.infoBox').addClass(animationName).delay(1000).queue(function(next){
        $('.infoBox').removeClass(animationName);
        next();
      });
    }
  },

とかいてます。

getDataはusersの情報を取得していて

その際にfetchを使っています。

fetch('/users/getusers')
      .then(dataWrappedByPromise => dataWrappedByPromise.json())
      .then(data => {
        this.users = data;
      })

という書き方でデータが取得できます。
ちなみに

users = data

とするとうまくいかずthisを付ける必要があります。

javascriptを使う人からしたら当たり前かもしれないですが自分はこれで結構はまりました。

usersに限らずmethods内でdata以下に書いたデータを処理しようとするとthisが必要になるっぽいです。

createUserでユーザの作成をしていてaxiosを使っています。

createUser: function() {
      if (!this.cword) return;

      for(var i in this.users) {
        var user = this.users[i];
        if (user.name.toLowerCase() == this.cword.toLowerCase()) {
          console.log("can not save user")
          return;
        }
      }

      axios.post('/users/create', { user: {name: this.cword, phone: this.pword, address: this.aword } }).then((response) => {
        var luser = {name: this.cword, phone: this.pword, address: this.aword };
        this.users.unshift(luser);
        this.cword = '';
      })
    },

もし名前がなかった場合や既に同じ名前のユーザーがいた場合はreturnして保存できなくしています。

ですがこれだとエラーメッセージが出ないのでalertとかで何かしらのエラーメッセージを出したほうが良いと思います。

それでreturnに引っかからなかった場合

axios.postでユーザーの作成をしています。
またリアルタイムで反映させるためにユーザが作成されたらusers.unshiftでuserを追加しています。

ちなみにresponse内に送ったデータがあるのでそれを使うと

例えば

axios.post('/users/create', { user: {name: this.cword, phone: this.pword, address: this.aword } }).then((response) => {
        this.users.unshift(respose.data.user);
        this.cword = '';
      })

みたいな感じでとれるっポイんですが自分の場合うまくいかず
response.config.dataで

{ "user":{name: "asdf", phone: "asdf", address: "asdf"}}

見たいな感じで取得できるのですが

var d = response.config.data
data.user

みたいにしてもデータはなぜかとれず仕方ないので

var luser = {name: this.cword, phone: this.pword, address: this.aword };

としています。

showUserに関しては

this.result = [];
      for(var i in this.users) {
        var user = this.users[i];
        if(user.name === d) {
          this.result = user;
        }
      }

でユーザを検索して検索結果をresultに入れています。

   var animationName = $('.infoBox').data('animate');

      $('.infoBox').addClass(animationName).delay(1000).queue(function(next){
        $('.infoBox').removeClass(animationName);
        next();
      });

でアニメーションを実装しています。

アニメーションに関してはanimate.cssを使っているのでアニメーションを使いたい場合は

ここからcssをとってきてください。

そしてcomputedにfilteredUsersを書いています。

computed: {
    filteredUsers: function() {
      var users = [];

      for(var i in this.users) {
        var user = this.users[i];

        if (user.name.toLowerCase().indexOf(this.keyword) !== -1) {
          users.push(user);
        }
      }
      return users;
    }
  }

indexOfが-1ではないとき、つまりマッチしている場合はusersにpushしています。

style

style部分も一応張っておきます。

body 
{
  background: rgba(241, 196, 15,0.4);
}

header
{
  width: 100%;
  height: 80px;
  line-height: 80px;
  font-weight: 800;
  background: rgba(230, 126, 34,1.0);
  color: white;
  text-align: center;
  margin-bottom: 50px;
}

header h3 
{
  line-height: 80px;
  font-size: 57px;
}
.top-heading 
{
  text-align: center;
  font-weight: 700;
  letter-spacing: 1px;
  border-bottom: 2px solid rgba(230, 126, 34,1.0);
  color: rgba(230, 126, 34,1.0);
  margin-bottom: 50px;
}
.usersList
{
  width: 100%;
}
.nameList 
{
  width: 100% !important;
  border-bottom: 1px solid #e67e22 !important;
  cursor: pointer;
  transition: all 0.5s;
}

.nameList:hover 
{
  background-color: #d35400;
  color: white;
  border-top-right-radius: 20px;
}

.infoBox
{
  width: 500px;
  height: 250px;
  border: 4px solid #d35400;
  background-color: white;
  margin: 0 auto;
  border-radius: 20px;
  margin-top: 100px;
  position: fixed;
  box-shadow: 1px 1px 3px #4e2b0c;
  box-shadow: 3px 3px 6px #976438;

}
.infoBox p 
{
  text-align: center;
  font-weight: 600;
  font-size: 26px;
  color: #d35400;
  border-bottom: 1px solid #b8632b;
}
#userInfo table 
{
  border:none!important;
  width: 95% !important;
  margin: 0 auto;
}
#userInfo table tr 
{
  border-bottom: 1px solid #b8632b;
}
.fas {
  color: rgba(230, 126, 34,1.0);
  font-size: 36px;
}

.col-left-item
{
  text-align: center;
  color: black;
  font-weight: 600;
}
.submit-btn 
{
  width: 50px;
  height: 50px;
  margin: auto;
  margin-bottom: 50px;

}
.submit-btn i 
{
  color: #d35400;
  transition: all 0.5s;
}
.submit-btn i:hover
{
  box-shadow: 0 0 10px #b8632b;
}
.search-form
{
  margin-bottom: 25px;
}

おわりに

これで多分うごくと思います。

正直javascriptの知識がないので結構いろんなところでつまずきました。

あとページをリロードするたびにvueファイルが変更されているとコンパイルをする感じだと思うのでstyleにかんしてはassetsのファイルに書いたほうがいいんじゃないのかと思います。

javascriptは嫌いだけどvue.jsはなんかパズルを組み立てていく感じがして好きですし面白いです。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3