20201018のRubyに関する記事は25件です。

【Ruby】配列

配列とは

  • 複数の情報を順番にまとめて管理するもの
  • 1つの変数で複数の値を持つことができる
  • 配列の中には複数の値を入れることができる

入力

names = ["田中","山田","佐藤"]

puts names

出力

田中
山田
佐藤

値の追加(<<)

配列の一番後ろに値を追加する。

入力

names << "鈴木"

puts names

出力

田中
山田
佐藤
鈴木

配列の値の取得

添字を指定し取得する。
入力

name = names[0]
puts name

name = names[2]
puts name

出力

田中
佐藤

配列の値の変更

入力

puts name[1]
names[1] = "佐々木"
puts name[1]

出力

山田
佐々木
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyでJSONからTSV、TSVからJSONへ変換

はじめに

やりたいこと

TSVファイルをJSON形式に、またJSONをTSVファイルに変換

環境

Ruby 2.6.5
Mac OS 10.15.5

この記事内で使用するサンプル

meibo.json
[{"name":"john","gender":"m","age":"18"},
 {"name":"paul","gender":"m","age":"20"},
 {"name":"alice","gender":"f","age":"15"},
 {"name":"dabid","gender":"m","age":"17"},
 {"name":"jasmin","gender":"f","age":"17"}]
meibo.txt
Name    gender  age
john    m   18
paul    m   20
alice   f   15
dabid   m   17
jasmin  f   17

JSONからTSVへ変換する方法

require "json" #JSONを扱うために記述

File.open("meibo.json") do |json| #JSONファイルを開く
  File.open("meibo.txt", "w") do |txt| #出力先のTSVファイルを開く
    array = JSON.load(json) #JSONの読み込み
    column = ["name","gender","age"] #ヘッダー
    txt.puts(column.join("\t")) #ヘッダを入れる
    array.each do |line| #JSON配列の各要素をTSVファイルの各行に入れる
      attr = [line["name"],line["gender"],line["age"]]
      txt.puts(attr.join("\t"))
    end
  end
end

まずは、JSONファイルを開きます。出力先のTSVファイルも書き込みモードで開いておきます。
次に、JSONファイルを読み込んでいきます。下記のように、JSONはJSON.load([Fileオブジェクト])でJSONファイルを読み込んで、ハッシュを要素とする配列で返してくれます。

require "json"

File.open("meibo.json") do |json|
  array = JSON.load(json)
  p array
end

#=>
[{"name"=>"john", "gender"=>"m", "age"=>"18"}, {"name"=>"paul", "gender"=>"m", "age"=>"20"}, {"name"=>"alice", "gender"=>"f", "age"=>"15"}, {"name"=>"dabid", "gender"=>"m", "age"=>"17"}, {"name"=>"jasmin", "gender"=>"f", "age"=>"17"}]

返ってきた配列をeachメソッドで回し、それぞれのハッシュをTSVでの一行一行にしていくイメージです。
TSVの各行に入れていくテクニックとしては、eachで取り出した各ハッシュのバリューを、キーを直接指定するかたちで取り出し、それらを一度、配列に入れ直します。そして、joinメソッドを用いてタブ区切りで配列を結合し、その文字列をTSVのファイルの各行に挿入していきます。

出力結果は以下の通りです。

meibo.txt
name    gender  age
john    m   18
paul    m   20
alice   f   15
dabid   m   17
jasmin  f   17

ハマったところ

最初は以下のように、JSON.loadしたあとの配列をeach処理に回す際、各オブジェクトのバリューの並び順通りに、TSVへ格納していきましたが、
JSONのメンバー(キーとバリューのセット)の並び順は関係ないとのこと。

つまり{"name":"john","gender":"m","age":"18"}{"name":"john","age":"18","gender":"m"}は区別しません。

よって、今回はたまたま、JSONのメンバーの並び順通りでTSVに変換しても、問題ないですが、メンバーの並び順がバラバラのJSONだと、TSVに変換した時おかしくなってしまうことがわかりました。

なので、面倒ですが一度、ハッシュのキーを指定する形でバリューを取り出し、それをカラムの並び順通りに配列にいれてから、TSVへ入れ込むと、確実です。

require "json" 

File.open("meibo.json") do |json|
  File.open("meibo.txt", "w") do |txt|
    array = JSON.load(json)
    column = ["name","gender","age"]
    txt.puts(column.join("\t"))
    array.each do |line|
      txt.puts(line.values.join("\t")) #JSONの各オブジェクトのバリューの並び通りにTSVへ入力
    end
  end
end

TSVからJSONへ変換する方法

require "json"
require "csv"

hash_ary = []

File.open("meibo.json","w") do |json|
  CSV.foreach("meibo.txt",col_sep: "\t", headers: true) do |line|
    h = {name: line[0], gender: line[1], age: line[2]}
    hash_ary << h
  end
  JSON.dump(hash_ary,json) #to_jsonを使用
end

まず、書き込みモードでJSONを書くためのファイルを用意します。

TSVファイルはCSVクラスでforeachメソッドを使って各行をハッシュに変換していきます。変換したハッシュは一度配列(ここでは、hash_ary)にいれて、全ての行が入れ終わったあとに、JSON.dumpでハッシュからJSONへの変換を行い、ファイルに書き込みます。

出力結果は以下の通り。

meibo.json
[{"name":"john","gender":"m","age":"18"},{"name":"paul","gender":"m","age":"20"},{"name":"alice","gender":"f","age":"15"},{"name":"dabid","gender":"m","age":"17"},{"name":"jasmin","gender":"f","age":"17"}]

ハマったところ

require "json"
require "csv"

hash_ary = []

File.open("meibo.json","w") do |json|
  CSV.foreach("meibo.txt",col_sep: "\t", headers: true) do |line|
    h = {name: line[0], gender: line[1], age: line[2]}
    hash_ary << h.to_json
  end
  json << hash_ary
end

最初は、上記のように書いてましたが、このようにすると。。

meibo.json
["{\"name\":\"john\",\"gender\":\"m\",\"age\":\"18\"}", "{\"name\":\"paul\",\"gender\":\"m\",\"age\":\"20\"}", "{\"name\":\"alice\",\"gender\":\"f\",\"age\":\"15\"}", "{\"name\":\"dabid\",\"gender\":\"m\",\"age\":\"17\"}", "{\"name\":\"jasmin\",\"gender\":\"f\",\"age\":\"17\"}"]

このように"の前に\が入ってうまく変換できませんでした。

to_jsonメソッドはJSON形式の文字列で返して来るので、これではなく、JSON.dumpを使用したら、解決しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveSupport の underscore は camelize の逆変換じゃない

複数の単語からなる言葉を識別子に使うとき,スペースを含めることができないので,何らかの方法で単語の区切りを表さなければなりません。
たとえば以下のようなさまざまな表記方法が使われています。

  • UpperCamelCase
  • lowerCamelCase
  • snake_case

ActiveSupport にはこれらの間の変換をする便利なメソッド群が用意されています。

CamelCase を snake_case に変換するには String#underscore が使えます。
snake_case を UpperCamelCase に変換するには String#camelize が使えます1

以下のように:

require "active_support/inflector"

p "BookTitle".underscore # => "book_title"
p "book_title".camelize  # => "BookTitle"

これを見ると,underscorecamelize は互いの逆変換になっており,

require "active_support/inflector"

p "BookTitle".underscore.camelize  # => "BookTitle"
p "book_title".camelize.underscore # => "book_title"

のように両者を重ねると(常に)元に戻るように思えます。

しかし,そうならない場合もあります。それは

require "active_support/inflector"

p "OfficialURL".underscore # => "official_url"
p "CSVFile".underscore     # => "csv_file"

URLCSV のように大文字だけで構成される単語を含むケースです。
underscore メソッドは,人間が期待するとおり,CSVFileCSVFile に分割して snake_case 化してくれました。
しかし,これにより,変換後の urlcsv がもともと大文字だけで綴られていたという情報が失われてしまうので,これらを camelize しても元には戻らないのです。

require "active_support/inflector"

p "OfficialURL".underscore.camelize # => "OfficialUrl"
p "CSVFile".underscore.camelize     # => "CsvFile"

便利なメソッドも仕様をよく理解して使わないと落とし穴に落ちる可能性がある,という例でした。
(つか,ハマったんだよ2,実際に!)


  1. lowerCamelCase に変換するには camelize(:lower) のようにします。 

  2. 当初 gsub(/.(?=[A-Z])/){ $& + "_" }.downcase みたいなコードで snake_case 化してて,あとで ActiveSupport を使った処理を混ぜたときに不具合が生じたという。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsで共通のレイアウトをまとめてみる

今回はrailsのhtmlファイルで共通のレイアウトを一つのファイルにまとめて書く方法をシェアしたいと思います。
viwes/layout配下にapplication.html.erbというファイルがあると思います。
そのファイルに以下のように共通のレイアウト部分を纏めてあげることで同じ内容のコードを書かずに済みます。

<!DOCTYPE html>
<html>
  <head>
    <title>Tweet</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
  <header>
  <div class="header-logo">
    <%= link_to("TweetApp","/") %>
  </div>
  <ul class="header-menus">
  <li>
    <%= link_to("TweetAppとは","/about") %>
  </li>
  <li>
  <%= link_to("投稿一覧","/posts/index") %>
  </li>
  </ul>
</header>
    <%= yield %>
  </body>
</html>

上記の

タグの中身は元々、他のhtmlファイルのものでした。
今回のように一つにまとめる方法もあるので知っておくと便利でしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]本番環境で画像が表示されないエラーについて

はじめに

ローカル環境ではしっかりと画像が表示されていたのに本番環境にデプロイすると画像が表示されなくなった時の対応。

環境

・Rails 6.0.3.
・Haml

状況

以下の表記みたいにRailsのヘルパーメソッドのimage_tagを用いて画像をパス指定していてローカル環境ではうまく表示されていた。

= image_tag "assets/material/icon/icon.png"

しかし、いざデプロイすると画像が表示されなくなりました。(この表記もあやしいですが)

もちろんscssファイルに記載しているbackground-imageも表記されませんでした(この対応ついては下の方に記載してます)

原因

本番環境では、画像もコンパイルされ、画像ファイル名が変わりディレクトリも変わってしまいます!

本番環境とローカル環境での違いは、
パスが変わる(app/assets/images/icon.png => /assets/icon.png)
名前が変わる(icon.png => icon-xxx… .png) ※xxx… はdigest
これが変わってしまいます!

対策

assets_path()というRailsのオプションを使用します。
これは便利で本番環境へデプロイしファイル名やディレクトリが変わってもそれに対応しているのでローカルも本番もこれ一つで対応できてしまいます!

具体的には以下のように修正

= image_tag asset_path("assets/material/icon/icon.png")

これでうまく行くと思いきや、、、、
ローカル環境でも表示されなくなりました。。。。

ダメ元でデプロイしても結果は同じ(当然です)

asset_pathでもうまくいかない原因

asset_pathというオプションはapp/assets/imagesの直下にあるファイルに対応しているが、どうやらその先のディレクトリの中にあるファイルには対応していないみたいです。

対応

ディレクトリで管理していた画像をすべてapp/assets/images直下へ移動し、表記も下記のように修正しました。

= image_tag asset_path("icon.png")

これでデプロイするとようやく画像が表示されました!!

まとめるとasset_pathというオプションを使うことと画像ファイルの場所をimages直下にすることが解決策でした。

[scss]background-imageについて

こちらも以下のようにただ画像ファイルのパスを指定してもうまくいきませんでした。

background-image: url("main-image.jpg");

対応

image-urlを使うとローカルでも本番環境でも対応してくれます。

background-image:image-url("main-image.jpg");

このように記述すると対応できます。
もちろん画像はapp/assets/images直下のものを指定

ここで注意が必要なのは:(コロン)の後はスペースを開けずに記述する点です。SCSSの記述はコロンのあとは大体半角スペースを開けて記述していたのでクセになってました。
なので誤っているとは知らずになかなか原因に辿り着けずにいました。

これでデプロイすると正常に画像が表示されました!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsチュートリアルでCloud9の容量がいっぱいになったときの対処法

対処法

Railsのチュートリアルの公式から、対処法が記載されています。
ただ、私が本エラーに遭遇した時、Railsのチュートリアルが記載してる対処法を見つけられなかったので、アクセスの多いQiitaに投稿しておきます。

RailsチュートリアルでCloud9の容量がいっぱいになったときの対処法

コメント

一人でも多く、本事象から抜け出して楽しくRailsチュートリアルを完走できることを祈ってます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]Userモデルの単体テストコード

はじめに

Rspecというgemを用いてテストコードを実装しました。
今回はユーザー登録機能の単体テストコードについて、正常系と異常系の挙動の確認を行いました。

目次

1必要なgemの導入
2FactoryBotの記述
3テストコードの記述

1. 必要なgemの導入

gemfileのgroup :development, :testの中に、以下を追記します。
その後、bundle installを実行します。

gemfile
gem 'pry-rails'
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'faker'

次に、Rspecを書くためのディレクトリを作成します。

ターミナル
rails g rpsec:install

実行すると、以下のようにディレクトリとファイルが生成されます。

ターミナル
create  .rspec
create  spec
create  spec/spec_helper.rb
create  spec/rails_helper.rb

テストコードの結果をターミナル上で確認するための記述をします。

.rspec
--format documentation

2. FactoryBotの記述

FactoryBotとはインスタンスをまとめておくことができるgemです。
specディレクトリにfactoriesディレクトリを作成し、その中にuser.rbファイルを作成します。
user.rbを以下のように編集します。

spec/factories/user.rb
FactoryBot.define do
  factory :user do
    nickname { Faker::Name.last_name }
    email { Faker::Internet.free_email }
    password = Faker::Internet.password(min_length: 6)
    password { password }
    password_confirmation { password }
  end
end

Fakerはランダムな値を生成するgemです。

3. テストコードの記述

spec/models/user_spec.rbを以下のように編集します。

spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe User do
    before do
      @user = FactoryBot.build(:user)
    end
    describe 'ユーザー新規登録' do
      context '新規登録がうまくいくとき' do
        it 'nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること' do
          expect(@user).to be_valid
        end
        it 'passwordが6文字以上あれば登録できること' do
          @user.password = '123456'
          @user.password_confirmation = '123456'
          expect(@user).to be_valid
        end
      end

      context '新規登録がうまくいかないとき' do
        it 'nicknameが空では登録できないこと' do
          @user.nickname = nil
          @user.valid?
          expect(@user.errors.full_messages).to include('ニックネームを入力してください')
        end
        it 'emailが空では登録できないこと' do
          @user.email = nil
          @user.valid?
          expect(@user.errors.full_messages).to include('Eメールを入力してください')
        end
        it '重複したemailが存在する場合登録できないこと' do
          @user.save
          another_user = FactoryBot.build(:user, email: @user.email)
          another_user.valid?
          expect(another_user.errors.full_messages).to include('Eメールはすでに存在します')
        end
        it 'passwordが空では登録できないこと' do
          @user.password = nil
          @user.valid?
          expect(@user.errors.full_messages).to include('パスワードを入力してください')
        end
        it 'passwordが存在してもpassword_confirmationが空では登録できないこと' do
          @user.password_confirmation = ''
          @user.valid?
          expect(@user.errors.full_messages).to include('パスワード(確認用)とパスワードの入力が一致しません')
        end
        it 'passwordが5文字以下であれば登録できないこと' do
          @user.password = '12345'
          @user.password_confirmation = '12345'
          @user.valid?
          expect(@user.errors.full_messages).to include('パスワードは6文字以上で入力してください')
        end
      end
    end
  end
end

以下のコマンドでテストコードを実行します。

ターミナル
bundle exec rspec spec/models/user_spec.rb 

今回は、'rails-i18n’を用いているためエラーメッセージが日本語表記になっています。
pry-railsというgemを用いることで、テストコードの実行を途中で止め、エラーメッセージの確認を行なっています。

参考リンク

https://github.com/rspec/rspec-rails
https://github.com/faker-ruby/faker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】RSpec導入まで

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

RSpecとは?

Railsで用いられることが多いテストツールであり、テストコードを書いておくと自動でアプリケーションの動作をテストしてくれます。
またgemでinstallすることが可能です。

RSpecの構成

  • describe:テストの題名
  • context:題名を詳細に分ける場合使用
  • before:itの内容を実行する前に必要な記述があれば使用
  • it:テスト内容

この中でも、describe と it は必須になります。

RSpecのインストール

Gemfileの group :test do の中を変更します。
デフォルトの記述は削除し、代わりに以下の4つのgemを記述します。

Gemfile
group :test do
  gem 'capybara', '>= 2.15'
  gem 'rspec-rails'
  gem "factory_bot_rails"
  gem 'faker'
end
ターミナル
$ bundle install
$ rails g rspec:install

実行後、app配下にspecフォルダが作られますので、
これを編集しながらテストを実行していきます。

また、config/environments/test.rb の下部にある下記を:silenceに変更します。

config/environments/test.rb
config.active_support.deprecation = :stderr

config.active_support.deprecation = :silence

そして下記を実行します。

ターミナル
$ rails db:migrate RAILS_ENV=test

また以下を追加するとテストを通過した文言も表示されるようになるため、
設定をおすすめします。

.rspec
--require spec_helper
--format documentation <--追加

ここまでで導入はOKです。

次回から
controller、model、view
をテストする方法を紹介します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] 投稿一覧をカテゴリー別で表示する方法

前提

  • Ruby 2.6.3
  • Rails 5.2.4.4
  • devise導入済(しなくてもいい)
  • 基本的なページは完成している
  • コントローラー名は今回gears
  • gearモデルにはcategoryカラムが存在する
  • 今回はログインユーザーにしか見えない投稿一覧なので、他ユーザーも閲覧できるようにしたい場合はuser_id: @user.idを省いてください。

やり方

Controller

編集前

gears_controller.rb
 def index
   @user = current_user
   @gear = Gear.where(user_id: @user.id)
 end

編集後

gears_controller.rb
  def index
    @user = current_user
    @gear1 = Gear.where(user_id: @user.id, category: "住居系")
    @gear2 = Gear.where(user_id: @user.id, category: "料理系")
    @gear3 = Gear.where(user_id: @user.id, category: "火周り系")
    @gear4 = Gear.where(user_id: @user.id, category: "その他")
  end

View

index.html.erb
<div class="category bg-success">住居系</div>
  <% @gear1.each do |gear| %>
    <div class="gear-index-item mb-20">
      <% if gear.image.attached? %>
        <%= image_tag gear.image, class: "index-img" %>
      <% else %>
        <img class="index-img" src="<%= "/images/default_gear.jpg" %>" alt="Index image cap">
      <% end %>
      <%= link_to(gear.name, "/gears/#{gear.id}") %>
    </div>
  <% end %>

  <div class="category bg-warning">料理系</div>
  <% @gear2.each do |gear| %>
    <div class="gear-index-item mb-20">
      <% if gear.image.attached? %>
        <%= image_tag gear.image, class: "index-img" %>
      <% else %>
        <img class="index-img" src="<%= "/images/default_gear.jpg" %>" alt="Index image cap">
      <% end %>
      <%= link_to(gear.name, "/gears/#{gear.id}") %>
    </div>
  <% end %>
   .
   .
   .

それぞれeach文で繰り返し処理を行う。

結果

image.png

まとめ

whereメソッドを使えば簡単に特定カラムでまとめられる!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がGitHubに自分のアプリを上げる方法

前提条件

Githubに登録していること
アプリがあること

大きな流れ

①ワーキングツリーを作成
②インデックスにファイルを登録
③ローカルリポジトリにファイルを登録
④リモートリポジトリと接続
⑤リモートリポジトリにファイルを登録

前提

GitHubにアップしたいファイルがあるフォルダにがカレントディレクトリであること

①ワーキングツリーを作成

git init

.gitが生成されていることを確認
ls -a

②インデックスにファイルを登録

git add *

備考
*はワイルドカードで、カレントディレクトリにある全てのファイルをインデックスに登録している

③ローカルリポジトリにファイルを登録

git commit -m 'test'

備考
-mはコミットに対してコメントを付ける機能でつけてもつけなくてもいいです。

確認
コミットされたことを確認
git log
Author: xxx
Date: Sun xxx xx 17:35:23 2020 +0900

test

④リモートリポジトリと接続

git remote add origin https://github.com/xxxxxxxx/xxxxxxxx.git


備考
originはリモートリポジトリ名で、任意の名前をつけても良い。基本はorigin
https/://github.com/xxxxxxxx/xxxxxxxx.gitはリモートリポジトリURL。GitHubのウェブページでリポジトリを作成すると付与される。(多分リポジトリページにそれらしきものはある)


確認
git remote -v

備考
接続しているリモートリポジトリ先が表示される

⑤リモートリポジトリにファイルを登録

git push origin master


備考
masterはブランチ名。デフォルトがmaster。
自分の今いるブランチを確認したい場合は以下コマンド
git branch --contains


確認
自分のGitHubのリポジトリページにアクセスしてアップロードされていることを確認

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby/Rust 連携 (7) インストール時ビルドの Rust 拡張 gem を作る

連記事目次

はじめに

今回は,ソースに Rust コードを含んだ gem の例。
以下の記事が参考になる。
Rust でつくるかんたん Ruby Gem - Qiita

上記の記事では,開発時に Rust のコンパイルを行い,生成物を gem のパッケージに含める,という形を取っていた。
それに対し,gem のインストール時に Rust のコンパイル(ビルド)を行う方法 を取ってみよう。
そのためには,当然インストール先の環境に Rust がインストールされていなければならない。

今回試作した gem を GitHub にさらす。gem 名は rust_gem_sample01 とした。
https://github.com/scivola/rust_gem_sample01

やり方を示すための試作品なので RubyGems.org には入れていない。

macOS,Linux,Windows のいずれでもインストールできるように書いたつもり。

題材

この gem は実用目的ではないので,なるべく単純な例を。
Ruby の組込みメソッドや標準添付ライブラリーには,一様分布以外の分布を持つ乱数生成機能が無いので,それをやろう。
とりあえず正規分布の乱数を返すやつ。

こんな感じで使えるもの:

require "rust_gem_sample01"

standard_deviation = 2.0

p RustGemSample01.rand_norm(standard_deviation)
# => -3.1674786173729506

RustGemSample01 モジュールに特異メソッド rand_norm を定義。引数に標準偏差を与えると平均値が 0 の乱数を返す。
正規分布は平均値と標準偏差の二つのパラメーターを持つが,平均値を変えたければ足し算すればいいだけなので,引数は標準偏差だけとした。引数が多いほど FFI のコストが大きくなるので,これでいいと思う。

インストールしてみよう

RubyGems.org には載せていないので,

gem install rust_gem_sample01

ではインストールできない。

以下の手順で,リポジトリーをクローンし,gem のビルド&インストールをしてみてほしい。
いろんな環境でちゃんとインストールできるかどうか知りたいので,協力していただけるとありがたい。

git clone https://github.com/scivola/rust_gem_sample01.git
cd rust_gem_sample01
rake install

この最後の rake install というのは,gem のプロジェクトファイルから gem のパッケージを作り1,システムに(つまりグローバルに)その gem をインストールする,というもの。

上を実行するためには,Git がインストールされていることと,Rust(バージョン 1.40.0 以上)がインストールされている必要がある2

インストールに失敗するとすれば,Rust コードのビルドのところか,ビルドの生成物を移動するところだと思う。

インストール時に Rust のコンパイルが行われるが,そこでは依存クレートのネットからのダウンロードなどなども行われるので,場合によって数十秒かかることもある。応答が無くても気長に待ってほしい。

sudo gem install の場合

Linux とかだと,システムワイドに Ruby をインストールしていて,gem をインストールするとき

sudo gem install hoge

のように sudo を付けなければならない場合も多いと思う。
この場合,cargo も sudo で動くようになっていなければならない。Rust を

使ってみよう

require "rust_gem_sample01"

n = 10000

bin = Hash.new(0)
n.times do
  bin[RustGemSample01.rand_norm(2.5).round] += 1
end

bin.keys.minmax.then{_1.._2}.each do |b|
  puts "%4d %s" % [b, "*" * (bin[b].fdiv(n) * 60 * 4).round]
end

これは,平均が 0,標準偏差が 2.5 の正規分布の乱数を 10000 回発生させ,その値を整数に丸めたもののヒストグラム(頻度分布図)を描くもの。
だいたいこんなものが出力される。

 -11
 -10
  -9
  -8
  -7 *
  -6 **
  -5 *****
  -4 ***********
  -3 ******************
  -2 ******************************
  -1 **********************************
   0 ************************************
   1 **********************************
   2 *****************************
   3 ******************
   4 ***********
   5 ******
   6 **
   7 *
   8
   9
  10

うん,なんかそれっぽいね。

もし,プログラムが動かなくてエラーが出るようなら教えて欲しい。

ファイル構成

.
├── bin
│  ├── console
│  └── setup
├── Cargo.toml
├── CHANGELOG.md
├── ext
│  └── Rakefile
├── Gemfile
├── lib
│  ├── rust_gem_sample01
│  │  └── version.rb
│  └── rust_gem_sample01.rb
├── LICENSE
├── pkg
├── Rakefile
├── README.md
├── rust_gem_sample01.gemspec
├── src
│  └── lib.rs
└── test
   ├── rust_gem_sample01_test.rb
   └── test_helper.rb

ちなみに,この図は Rust 製のコマンドラインツール exa で作った。

gem 用ファイルと Rust パッケージ用ファイルの混在

見てのとおり,gem のためのファイルと Rust パッケージのファイルが同階層に混在している。
かいつまんで説明すると,bin, lib, pkg, Rakefile, rust_gem_sample01.gemspec, test あたりが gem のファイル。
Cargo.toml, src が Rust パッケージのファイル。Rust のビルドを行うと target ディレクトリーなんかも出来る。

ファイル名,ディレクトリー名がかぶらないので,これで問題ないわけだが,これがよい流儀かどうかは判らない。Rutie のサンプルなんかはこういう構成になっていた(たしか)。
少なくとも初見で直ちに区別ができるかというと,心もとない。
Rust 関係を一つのディレクトリーに押し込めたほうがいいかもしれない3

Rust コード

Rust コードはライブラリークレート rand_distr_for_ruby だけを持つパッケージとなっている。
依存クレートは

Cargo.toml
[dependencies]
rand = "0.7.3"
rand_distr = "0.3.0"

だけ。
rand はお馴染み。rand_distr のほうは,もともと rand に入っていた,正規分布,コーシー分布,二項分布,ポアソン分布などなどの〈一様でない〉分布の乱数を発生させる関数を独立させたものらしい(知らんけど)。

コード本体はこれだけ:

src/lib.rs
use rand_distr::{Normal, Distribution};

#[no_mangle]
pub extern fn rand_norm(variance: f64) -> f64 {
    let normal = Normal::new(0.0, variance).unwrap();
    normal.sample(&mut rand::thread_rng())
}

呼ばれるたびに rand_distr::Normal を生成しているのは無駄な気がする。
まあ実用目的じゃないので。

同じ分布で多数の乱数を発生させるなら,ここは一度で済ませたい。生成した rand_distr::Normal をずっと保持するのは,Rutie を使えば簡単にできるが((5) 参照),FFI だけでどうやるのかよく分からない。

さて,開発中は,この状態で

cargo check

だの

cargo build --release

だのできる。
プロジェクトのルートディレクトリーに Rust のファイルを置くことで,階層を行ったり来たりしなくてすむのは,このディレクトリー構成の利点。

Ruby コード

Ruby 側の中核となるコードがこれ。

rust_gem_sample01/lib/rust_gem_sample01.rb
require "ffi"
require "rust_gem_sample01/version"

module RustGemSample01
  extend FFI::Library

  lib_name = "rand_distr_for_ruby"
  file_name =
    case RbConfig::CONFIG["host_os"].downcase
    when /darwin/      then "lib#{lib_name}.dylib"
    when /mingw|mswin/ then "#{lib_name}.dll"
    when /cygwin/      then "cyg#{lib_name}.dll"
    else                    "lib#{lib_name}.so"
    end

  ffi_lib File.expand_path(file_name, __dir__)

  attach_function :rand_norm, [:double], :double
end

ffi という gem を使って Rust の関数を Ruby のメソッドに割り当てている。
FFI(Foreign Function Interface)という,異なる言語間でやり取りするための仕組みを簡単に扱えるようにしたもの。

このモジュール定義のコードの大部分が,ffi_lib メソッドに渡す引数の決定に費やされている。
このメソッドには読み込むべきライブラリーファイルのパスを渡す。
Rust でコンパイルして出来たライブラリーファイルは,OS によってファイル名が少し異なるのでこんなややこしいことになっているのだ。
この処理が本当に合っているのかよく分からないが,Rutie のコード rutie-gem/lib/rutie.rb を参考にした。

attach_function :rand_norm, [:double], :double

の部分は,読み込んだライブラリーに基づいて,モジュールに Ruby のメソッドを生やす。
第一引数はライブラリーの関数の名前。これが Ruby のメソッドの名前にもなる。
第二引数は関数の引数の型。引数は複数ありうるので配列で与える。:double というのは,FFI の仕様で決められた型の名前で,倍精度浮動小数点数を表す。よくは知らないけど C 言語の double のことだろう。Ruby の Float がこれに対応する。
第三引数は関数の返り値の型。

Rust で作ったライブラリーの関数を,FFI を通して Ruby 側で利用するのは,もうホントにこれだけで済む。簡単。

gem に仕立てる

gemspec

gemspec について,とくに説明を要するところはここ。

rust_gem_sample01/rust_gem_sample01.gemspec
# 抜粋
Gem::Specification.new do |spec|
  # 中略
  spec.add_dependency "ffi", "~> 1.13.1"
  spec.extensions = %w[ext/Rakefile]
end

ffi gem を使っているのでそれをランタイム依存性に追加する(add_dependency)のは当然として,重要なのはその次の

spec.extensions = %w[ext/Rakefile]

のところ。これはなんじゃらほい?

これは gem のインストール時にやるべきことを記述した Rake ファイルのパスを指定したもの。
C 拡張の場合,その役目はふつう extconf.rb というファイルが担う。sqlite3 gem であれば sqlite3-ruby/ext/sqlite3/extconf.rb がそれにあたる。
全然よく分かってないのだが,C 拡張はこのファイルにしたがって C のコンパイルやらなんやらをやっているらしい。

しかし,どうやら extconf.rb は C を前提としているらしく,Rust の場合にどう書くのかよく分からない。というか,そもそも Rust では使えないような気がした。
他に手段はないのかと探してみると,どうも Rake タスクを使う方法もあると分かった。次節で具体的に述べる。

Rakefile

gemspec で spec.extensions = として指定した Rake ファイルに,default タスクとして書いたことがインストール時に実行されるらしい。

rust_gem_sample01/ext/Rakefile
# gem のインストール時の処理
# default タスクとして記述する
task :default do
  # Rust がインストールされているか
  # cargo コマンドが動作するかで判断
  begin
    cargo_v = `cargo -V`
  rescue Errno::ENOENT
    raise "Cargo not found. Install it."
  end

  # Rust のバージョン(Cargo のバージョンと一致)が一定以上か
  cargo_version = cargo_v.match(/\Acargo (\d+)\.(\d+)\.(\d+) /)[1..3].map(&:to_i)
  if (cargo_version <=>  [1, 40, 0]).negative?
    raise "Too old Cargo (ver. #{cargo_v}). Update it."
  end

  # Rust のビルド
  system "cargo build --release", chdir: __dir__ + "/.."

  # 生成物のファイル名
  # OS によって異なる
  lib_name = "rand_distr_for_ruby"
  file_name =
    case RbConfig::CONFIG['host_os'].downcase
    when /darwin/      then "lib#{lib_name}.dylib"
    when /mingw|mswin/ then "#{lib_name}.dll"
    when /cygwin/      then "cyg#{lib_name}.dll"
    else                    "lib#{lib_name}.so"
    end

  # 生成物を lib/ 直下に移動
  FileUtils.mv __dir__ + "/../target/release/#{file_name}", __dir__ + "/../lib/"
  FileUtils.rmtree __dir__ + "/../target/"
end

コード中にコメントを書いたが,やっていることは要するに

  • Rust 入ってる?
  • Rust のバージョン OK?
  • ビルド
  • ライブラリーファイルの移動

と,ただこれだけ。
Rust コードのビルドの生成物のファイル名を得るところが lib/rust_gem_sample01.rb とかぶってて DRY じゃないのが気になるけど。
まあ,target/release/ 直下のファイルを全部移動してもいいのかもしれない。

ところで,Rust でビルドを行うと,必要なライブラリーをダウンロードしてコンパイルして,という過程でいろいろなファイルが出来る。今回の gem の場合,18 MB くらいのファイルがビルド時に生成される。欲しいのはファイル一つ(今回は 300 KB 程度)で,ほかは全部不要。

そこで,タスクの最後の行

FileUtils.rmtree __dir__ + "/../target/"

によりお掃除している。

おわりに

C 拡張の場合,gem にコンパイル済みのファイルを含めて配布すべきか否かについて議論がある。

参考:

一長一短があるが,Rust の場合はどうだろう。
おそらくふつうは事前コンパイルを選ぶのだろう。だって,Ruby ユーザーの多くは Rust のコンパイル環境を持っていないだろうから。

なので,この記事ではあえて多くの人がやらなさそうな方法を試してみた。


  1. gem のパッケージを作るのは build という別の Rake タスクなのだが,install タスクが build タスクに依存しているので,rake install だけで両方やってくれる。 

  2. Rust のバージョンを 1.40.0 以上としたのは全く何の根拠もない。もっと低いバージョンでも何ら問題ないはずだが,どのバージョンで何がどうなったか把握してないので,最新版(現時点で 1.46.0)よりちょっと古いものにしてみた。バージョン下限は ext/Rakefile の 9 行目あたりで設定している。 

  3. ゼロから gem を作る場合に,そのほうが作業しすいかもしれない。最初に bundle gem hoge とやって gem のプロジェクトを作り,その中に入って cargo new fuga --lib とやって Rust のプロジェクトを作ればいいから。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

whereメソッドを使って別のモデルの値を参考に絞り込みを行う。

概要

whereメソッドを使った複数の条件の絞り込みを行ったのでまとめる。

やりたいこと

productモデルが作成された時間の直前に作成されたdeveloperモデルの情報を一つ取得したい。

前提条件

Product(製品)モデルとDeveloper(開発者)を作成。この2つのモデルは関連付けされていない。(ここはあえてです)
Developerモデルはどんな製品を作ったのかの情報(developed_product_number)を持っている。productには製品ナンバー(number)がついておりこの2つの値は等しい。
(だったら関連付けさせようよって話ですが今回はこの条件でいきます。)
同一の製品を作成した複数の開発者の中から製品ができる直前に作成された開発者の値を取得したい。

実装

ではまず実際のコードを載せます。

product = Product.find(params[:id])


@Developer = Developer.where(developed_product_number: product.id).where('created_at < ?', product).order(created_at: :desc).limit(1) 

こんな感じです。解説いきます

product = Product.find(params[:id])

対象となるproductのIDを取得して変数productに代入。
ここで変数@Developerに条件をつけて絞り込み検索をした値を入れます。

@Developer = Developer.where(developed_product_number: product.id).where('created_at < ?', product).order(created_at: :desc).limit(1) 

部分的に分けます。

@Developer = Developer.where(developed_product_number: product.number)

ここでは先程取得した変数productのidを取得しています。

次に値を絞り込みます。

where('created_at < ?', product).order(created_at: :desc).limit(1) 

ここでproductが作成された時間より前に作成された複数の開発者の情報取得し、降順から数えて1つ目の開発者の情報を取得しました。
かなり長くなってしまったのでここのコードはスコープなどに切り分ける必要がありますね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データベースになくても、データとして扱える活動的なハッシュ

はじめに

 アンケートなんかでよく見かける、都道府県をプルダウンから選択させる機能。、form_withメソッドを使用して投稿する機能をもたせることができる。しかし、その都道府県はテーブル上では数値で管理されている方が使い勝手がよい。数値で管理できるようにするには、予め、都道府県名とそれに紐づく数字を用意しておく必要がある。そのようなときに便利な機能が、ActiveHashである。

ActiveHashとは

 基本的に変更することのないデータをモデル内に記入することで、データベースへ保存せずにデータを取り扱うことができるようになるGem。主に、ビューで、プルダウンメニューとして表示させたいときに使う。

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

準備の流れ

1. Gemの記述
2. Gemをインストール
3. モデルを生成

1. Gemの記述

ファイルのいちばん下の行でOK

Gemfile
gem 'active_hash'

2. Gemをインストール

% bundle install

3. モデルを生成

% rails g model モデル名 --skip-migration

モデル名には、プルダウンメニューを一括りにするような名前が望ましい。
ex)野球、サッカー、テニス…
        のようなプルダウンを作成する場合は、モデル名はsportとなる。

--skip-migrationオプションをつけることで、マイグレーションファイルを生成しなくなる。ActiveHashはデータベースに保存しないので、マイグレーションファイルを必要としない。

以後、スポーツのプルダウンメニューを例に説明する。

記述から表示までの流れ

4. クラスを定義し継承する
5. プルダウンメニューの項目を記述
6. アソシエーションを記述
7. バリデーションを記述
8. プルダウンメニューを表示

4. クラスを定義し継承する

「3. モデル生成」で作られたモデルを以下の記述に変更する。

app/models/sport.rb
class Sport < ActiveHash::Base
  self.data = []
end

ActiveHash::Baseを継承することで、ActiveRecordのメソッドが使用できる。

5. プルダウンメニューの項目を記述

プルダウンの中身は、配列にハッシュで入れる。

app/models/sport.rb
class Sport < ActiveHash::Base
  self.data = [
    { id: 1, name: '---' },
    { id: 2, name: 'Baseball' },
    { id: 3, name: 'Soccer' },
    { id: 4, name: 'Tennis' }
  ]
end

6. アソシエーションを記述

以下、すでに、別の投稿したものを保存するテーブルが存在する前提の説明。

app/models/post.rb
class Post < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
    belongs_to_active_hash :sport
end

extend ActiveHash::Associations::ActiveRecordExtensionsの記述によって、belongs_to_active_hashメソッドが使えるようになる。

sportモデルにアソシエーションの記述の必要はない。

※通常のアソシエーションと記述が異なる点に注意

7. バリデーションを記述

app/models/post.rb
class Post < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
    belongs_to_active_hash :sport

  validates :sport_id, presence: true #空の投稿を保存できない


  validates :sport_id, numericality: { other_than: 1 } 
                        #プルダウンの選択が「--」の時は保存できない
end

8. プルダウンメニューを表示

collection_selectメソッド

collection_select(保存されるカラム名, オブジェクトの配列, カラムに保存される項目, 選択肢に表示されるカラム名, オプション, htmlオプション)
collection_select(:sport_id, Sport.all, :id, :name, {}, {class:"sport-select"})

保存されるカラム名:テーブルのどのカラムに保存をするのか。
オブジェクトの配列:配列のハッシュ全て表示させる場合は、.allでOK
カラムに保存される項目:app/models/sport.rbの中で、id:としていた部分を、カラムに保存させたい
選択肢に表示されるカラム名:プルダウンでapp/models/sport.rbname:としていた部分を表示させる
オプション:必要に応じて
htmlオプション:クラス名など

最後に

 とにかく、collection_selectメソッドの引数が多すぎて、混乱しそう…

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「ruby」2重ハッシュの取り出し ※復習

ruby.rb
user_data = [
  {

 user: {
        profile: {
            name: 'takeshi'
        }
    }
  },
  {
    user: {
        profile: {
          name: 'tanaka'
        }
    }
  },
  {
    user: {
        profile: {
            name: 'tosiki'
        }
    }
  }
]


内容

上記のコードには配列の中に個別のデータが二重ハッシュで存在します。
ユーザー名だけを全て順番にしたい!!!

実装

1.

ruby.rb
#eachメソッド
使用しないパターン

puts user_date[0]["user"]["profile"]["name"]
puts user_date[1]["user"]["profile"]["name"]
puts user_date[2]["user"]["profile"]["name"]

2.

ruby.rb
#eachメソッド使用パターン

user_date.each do |date|
  puts date [:user][:profile][:name]
end
!重要

ハッシュが複数重なっている時はハッシュの1番最初の”キー”から順に連続で表示したいキーまで指定していくことでnameキーのバリューを表示できる。

結果

見事に名前だけ出力することができました!!
1. eachメソッドを使用しない場合は1つずつ添字を指定しキーを連続で記述した
2. eachメソッドを使用し簡潔にキーを連続で指定することができた

???????????@MacBook-Pro ruby % ruby posi.rb
takeshi
tanaka
tosiki

考察

1番上のコードの記載方法だと見にくく感じるは初心者だからなのかもしれないが、下記の様なコードの記述方法に変換して見た、、、

user_date = [
  { user: { profile:  { name: "takeshi" }}},
  { user: { profile:  { name: "tanaka" }}},
  { user: { profile:  { name: "tosiki" }}}
]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】お気に入り登録とお気に入りした一覧を表示する巻

はじめに

ネットショッピングしてていいなと思った商品見つけたらお気に入り登録したことありますよね?
そのとき、画面が更新されずにお気に入りやいいねすると♡マークの色が変わったりしますよね。あとは、お気に入りした商品を一覧で確認できたらいいですよね。今回はその実装のメモ書きです。

※出品や投稿機能(itemsテーブル)及びログイン機能(usersテーブル)が備わっていることを前提にお話を進めます。

実装の手順(同期編)

完成のイメージはクリックした時に画面の一部が更新されて数字のカウントが増えていく・・・みたいなイメージ。ついでに削除機能も搭載します。お気に入りに追加したものは一覧で確認できるページも作成しちゃいます。

まずは、itemsテーブルとusersテーブルの中間テーブルとして
likesテーブルを作成。

↓追加するカラムとアソシエーションはこんな感じ↓

itemsテーブル

Column Type Options

===略===
※追加なし※

Association

  • has_many :likes
  • has_many :liked_users, through: : likes, source: :user #お気に入一覧作りたい時使うが今回は使わない

likesテーブル #中間テーブル

Column Type Options
item_id integer null: false
user_id integer null: false

Association

  • belongs_to :item
  • belongs_to :user

usersテーブル

Column Type Options

===略===
※追加なし※

Association

  • has_many :likes
  • has_many :liked_items, through: :likes, source: :item #お気に入一覧作りたい時使うが今回は使わない

これにてDB設計は完了
つづいてモデルの作成。以下のコマンドをターミナルに入力

rails g model like

テーブルを作成するためマイグレーションファイルを編集

2XXXXXXXXXXXX5_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer :item_id, null: false
      t.integer :user_id, null: false
    end
  end
end

からのrails db:migrateします。
そしたらモデルが生成されるのでアソシエーションを書いていきます。

user.rb
has_many :likes #追加
item.ruby
has_many :likes #追加
like.rb
class Like < ApplicationRecord

  belongs_to :item
  belongs_to :user

  validates :user_id, presence: true
  validates :item_id, presence: true
  validates_uniqueness_of :item_id, scope: :user_id

  def self.like_method(item, user)
    Like.find_by(item_id: item.id, user_id: user.id)
  end

end

self.like_method(item, user)
ここでIF分の設定をしちゃいます。
likeテーブルにitem_idとuser_idに入ってるーーーって条件を設定します!
次にコントローラーを作成するため以下のコマンドを実行

rails g controller likes

無事にコントローラーが作成されたら、
・ビューファイル
・コントローラー
・ルーティング
それぞれ設定していきます。

routes.rb
ーー略ーー  
resources :items, only: [:new, :create, :edit, :update, :show, :destroy] do
    resources :likes, only: [:create, :destroy]
  end
ーー略ーー 
  resources :users, only: [:show] do
    resources :likes, only: [:index]
  end
ーー略ーー 

createとdestroyはitemsにネスト
indexはusersにネスト

likes_controller.rb
class LikesController < ApplicationController

  before_action :set_item, only: [:create, :destroy]

  def index
    @items = Item.includes(:item_images).order("created_at DESC")
  end

  def create
    @like = Like.create(item_id: params[:item_id],user_id: current_user.id)
    redirect_to item_path(@item.id)
  end

  def destroy
    @like = Like.find_by(item_id: params[:item_id],user_id: current_user.id)
    @like.destroy
    redirect_to item_path(@item.id)
  end

  def set_item
    @item = Item.find(params[:item_id])
  end

end

createとdestroyはビューファイルがないのでリダイレクトの設定が必要になります。

index.html.haml
ーー略ーー  
      - if user_signed_in? && Like.like_method(@item, current_user)
        .check_btn__like--add{data: {item_id: @item.id}}
          = link_to "/items/#{@item.id}/likes/:id" , method: :DELETE do
            = icon( "fa", "star")
            お気に入り
            = @item.likes.length
      - elsif user_signed_in?
        .check_btn__like{data: {item_id: @item.id}}
          = link_to "/items/#{@item.id}/likes", method: :POST do
            = icon( "fa", "star")
            お気に入り
            = @item.likes.length
      - else
        .check_btn__like
          = link_to new_user_session_path do
            = icon( "fa", "star")
            お気に入り
            = @item.likes.length
ーー略ーー  

お気に入り登録する時の画面はこちら

index.html.haml
.mypage_Wrapper
  %section.mypage_title
    お気に入り一覧
  %section.like_Wrapper
    .like__box
      - @items.each do |item|
        - if user_signed_in? && Like.like_method(item, current_user)
          .like__box__product
            .like__image
              = link_to item_path(item.id) do
                = image_tag item.item_images[0].image.url
            .like__name
              = item.name
            %ul
              %li
                = item.price.to_s
              %li
                = "円"
              %li.like__tax
                = "(税込)"
              %li.like__icon
                = link_to "/items/#{item.id}/likes/:id" , method: :DELETE do
                  = icon("fas", "star")
.bottom_Wrapper

これが、お気に入りした商品一覧で表示するもの

like.scss
.like_Wrapper {
  width: 100%;
}

.like__box {
  background-color:white;
  margin: 0 auto;
  width: 65%;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  &__product {
    width: 180px;
    height: 90%;
    margin: 25px 2% 0;
    transition: all .3s ease-in-out;
    .like__image {
      width: 180px;
      height: 140px;
      margin-bottom: 3%;
      img{
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }
    .like__name {
      font-weight: bold;
    }
    ul{
      display: flex;
      font-size: 16px;
      li{
        font-weight: bold;
      }
      .like__tax {
        font-size: 10px;
        writing-mode: lr-tb;
        width: 30px;
        line-height: 30px;
        margin-left: 2%;
        margin-right: 24%;
      }
      .like__icon {
        i {
          font-size:1.1em;
          color: orange;
        }
      }
    }
  }
  &__product:hover {
    cursor: pointer;
    transform: scale(1.1, 1.1);
  }
}

一応、CSSも
ここまでの実装で同期した状態で実装できるかと思います!
いったんここまで、Ajaxを利用した非同期通信を実装するとよりリッチになるのでそのことを書いて行けたらいいなと・・・
それはまた後日・・・

Image from Gyazo

Image from Gyazo

参考

https://techtechmedia.com/favorite-function-rails/#i
https://qiita.com/naberina/items/c6b5c8d7756cb882fb20
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9
https://qiita.com/shh-nkmr/items/48fe53282253d682ecb0

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向まとめ

前置き

今更ながらオブジェクト指向の概要をまとめていく。私もオブジェクト指向を理解するのに苦しんだのでなるべく初学者にわかりやすく簡潔に書いていきます。
マサカリ上等ですのでコメントお待ちしております。

1.オブジェクト

オブジェクトとは「対象」「物」という意味で、プログラミングではデータ処理の集まりのことを指している。
Rubyでいうならば、配列やHash、数字や文字列など全てのデータは(モノ)オブジェクトである

2.オブジェクト指向

オブジェクト指向プログラミングとは、モノ(オブジェクト)の作成と操作として見る考え。

3.クラス(class)

クラス(class)はオブジェクト指向において、データと処理をひとまとめにした設計図のようなもの。
基本的にクラスを元にしてオブジェクトを生成することで使えるようになる

4.プロパティ(property)

オブジェクトが持っているデータのことをプロパティ(属性)という。

5.メソッド(method)

オブジェクトが持っている処理のこと。人でいえば「走る、歩く、止まる」などのオブジェクトが何らかのアクションを起こす処理の事

def メソッドの名前
  やりたい処理
end

例えば「こんにちは」と呼びだすhelloメソッドを定義するのであれば

def hello
  puts 'こんにちは'
end

hello #=> こんにちは

 といった感じになる。

6.インスタンス(instance)

インスタンスとはクラスから生成されたオブジェクトの事。
インスタンス作成の際にはnewメソッドを使用します。

class human
  def initialize(name)
    @name = name
  end

  def hello
   puts "こんにちは"
  end
end

human = human.new("徳川家康")

ここで定義されているinitializeメソッドはインスタンスが生成された時に自動で実行されるメソッドです。
これでnameという属性に「徳川家康」という値がセットされました。

humanクラスから生成されたhumanというインスタンスは徳川家康という固有の名前をもち、helloというメソッドを使うことができます。
この1行だけでこれだけの情報を持ったオブジェクトが作成できるのできます。

7.カプセル化

オブジェクトが持つデータや処理のうち、別のオブジェクトから直接利用される必要のないものを隠すことを言い、利用する場合は外部から操作するために作られた処理を設けることを言う。先ほどのレーシングカーの例でも出てきた、オブジェクト指向の基本概念。

カプセル化にはpublicとprivateがあります。
publicに記述したメソッドなどは外部からのアクセスは可能だが、privateを指定すると、呼び出す際にエラーとなります。

publicの場合

class human
  #public => 何も指定しなければrubyはデフォルトでpublicになる。基本的に省略可能。
  def initialize(name)
    @name = name
  end

  def hello
   puts "こんにちは"
  end
end

human = human.new("徳川家康")
puts human.name #=> 徳川家康が出力される

privateの場合

class human
  private
  def initialize(name)
    @name = name
  end

  def hello
   puts "こんにちは"
  end
end

human = human.new("徳川家康")
puts human.name #=> エラーが出力される

8.継承

特定のオブジェクトの機能を引き継いで使う事。
似たようなオブジェクトを複数作る時に、全てのプロパティやメソッドをいちいちプログラミングするのは非常に手間が掛かるが、継承を使うことにより、同じ機能を実装できる。

class Human
  def work
    puts '歩きました'
  end
end

#Humanクラスを継承
class IeyasuTokugawa < Human
  def unification
    puts "天下統一しました"
  end
end

IeyasuTokugawa = IeyasuTokugawa.new
puts IeyasuTokugawa.unification #=> 天下統一しました
puts IeyasuTokugawa.work #=>歩きました

クラスを継承した場合、クラス自体で定義されたメソッドの他に親クラスで定義されているメソッドも使用することが出来ます。先ほどの例で言えば「IeyasuTokugawa」クラスのオブジェクトは「unification」メソッドの他に親クラスの「work」メソッドも実行することが出来ます。

クラス内で定義されたメソッドだけではなく、スーパークラス(親クラス)で定義されているメソッドも実行できることが確認できます。

9.ポリモーフィズム

ポリモーフィズムとは(Polymorphism)とは、オブジェクト指向プログラミングにおける概念、手法の一つです。日本語で「多様性」とも言います。
異なるクラスに対し、同一のインターフェースが提供されていることを言います。

クラスを使う側は、クラスの実態を意識せずにメソッドを呼び出せます。

class Human

  def speak(voice='')
    "#{self.name}: #{voice}"
  end
end

#継承
class NobunagaOda < Human
  def speak(voice='鳴かぬなら 殺してしまえ ホトトギス')
    super
  end
end

class IeyasuTokugawa < Human
  def speak(voice='水よく船を浮かべ 水よく船を覆す')
    super
  end
end

NobunagaOda = NobunagaOda.new
NobunagaOda.name = '織田信長'

IeyasuTokugawa = IeyasuTokugawa.new
IeyasuTokugawa.name = '徳川家康'

NobunagaOda.speak    #=>織田信長: 鳴かぬなら 殺してしまえ ホトトギス
IeyasuTokugawa.speak     #=>徳川家康: 水よく船を浮かべ 水よく船を覆す

同じspeakメソッドであっても、クラスでの定義に応じて挙動の変化することがわかりました。

10.オブジェクト指向のメリット・デメリット

メリット

1.プロブラムのメンテナンスがしやすくなる

 ・プログラムの一つ一つを小さなまとまりで考える事が出来る為、複雑なプログラムが減ります。改修による影響度が減り、メンテナンスがしやすくなります。

2.分業化ができる

 ・システムの規模が大きくなるほど、プログラムの実装は膨大になります。オブジェクト指向は平行で大人数での実装が可能の為生産性が上がります。また似た機能等を他に影響を与える事なく機能を実装することも可能です。

3.プログラムの品質が高くなる

 ・あらゆるところが部品化されている為に問題箇所の特定が容易です。まとまりのあるプログラムのためコードの可読性も高く直感的にわかりやすい。

デメリット

1.難易度が高く教育コストがかかる

 ・開発現場で活躍する為には付け焼き刃の知識では役に立たない。抽象的な概念が多く、しっかり理解する為にはそれなりに
  現場経験が必要

2.設計力が試される

 ・継承やポリモーフィズムなど便利な概念が使用できる代わりに、他のコードとの影響性や拡張性、共通化などより可読性高く品質を上げる為には設計力が必要不可欠。

最後に

私もまだまだ初学者のため技術力がありません。プログラムを書いている際に基本的概念は少なくとも頭に入れておくことが重要だと思います。他の初学者の皆さんも一緒に覚えていって欲しいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

post to Qiita patching version

すでにqiitaにあげた記事を更新するversion

に最初に投稿するversionの作成記録があります.さらに,すでにqiitaにあげた記事を更新するversionです.下のcodeを

ruby post_final.rb post_final.org 

なんかでできます.最初のpostでは新しいのを作り,そのあとitemsのidをorgに記します.それがあるとそのidにpatchします.

さらに,'open', 'teams'を選べます.defaultは'private'.

ruby post_final.rb post_final.org teams

なんてね.

keyは

code

#! /usr/bin/env ruby

 1  require "net/https"
 2  require "json"
 3  
 4  def get_title_tags(src)
 5    $conts = File.read(src)
 6    title =  $conts.match(/\#\+(TITLE|title|Title): (.+)/)[2] || "テスト"
 7    m = []
 8    tags = if m =  $conts.match(/\#\+(TAG|tag|Tag|tags|TAGS|Tags): (.+)/)
 9         m[2].split(',').inject([]) do |l, c|
10        l << {name: c.strip} #, versions: []}
11      end
12       else
13         [{ name: "hoge"}] #, versions: [] }]
14       end
15    p tags
16    return title,tags
17  end
18  
19  src = ARGV[0] || 'README.org'
20  title, tags = get_title_tags(src)
21  p title
22  p tags
23  
24  system "emacs #{src} --batch -l ~/.emacs.d/site_lisp/ox-qmd -f org-qmd-export-to-markdown --kill"
25  
26  lines = File.readlines(src.gsub('org','md'))
27  lines << "------\n - **source** #{Dir.pwd}/#{src}\n"
28  
29  m = []
30  patch = false
31  if m = $conts.match(/\#\+qiita_id: (.+)/)
32    qiita_id = m[1]
33    patch = true
34  else
35    qiita_id = ''
36  end
37  
38  
39  case ARGV[1]
40  when 'teams'
41    qiita = 'https://nishitani.qiita.com/'
42    p access_token = ENV['QIITA_TEAM_WRITE_TOKEN']
43    private = false
44  when 'open'
45    qiita = 'https://qiita.com/'
46    p access_token = ENV['QIITA_WRITE_TOKEN']
47    private = false
48  else
49    qiita = 'https://qiita.com/'
50    p access_token = ENV['QIITA_WRITE_TOKEN']
51    private = true
52  end
53  
54  params = {
55    "body": lines.join, #"# テスト",
56    "private": private,
57    "title": title,
58    "tags": tags
59  }
60  
61  if patch
62    path = "api/v2/items/#{qiita_id}"
63  else
64    path = 'api/v2/items'
65  end
66  p qiita+path
67  uri = URI.parse(qiita+path)
68  
69  http_req = Net::HTTP.new(uri.host, uri.port)
70  http_req.use_ssl = uri.scheme === "https"
71  
72  headers = {"Authorization" => "Bearer #{access_token}",
73    "Content-Type" => "application/json"}
74  if patch
75    res = http_req.patch(uri.path, params.to_json, headers)
76  else
77    res = http_req.post(uri.path, params.to_json, headers)
78  end
79  
80  p res.message
81  
82  res_body = JSON.parse(res.body)
83  res_body.each do |key, cont|
84    if key == 'rendered_body' or key == 'body'
85      puts "%20s brabrabra..." % key
86      next
87    end
88    print "%20s %s\n" % [key, cont]
89  end
90  system "open #{res_body["url"]}"
91  qiita_id =res_body["id"]
92  unless patch
93    File.write(src,"#+qiita_id: #{qiita_id}\n"+$conts)
94  end

えっと,refactoringしてください.

課題

openとteamsで二箇所に出したいときは,どっちがどっちかわからなくなりますね.その辺り,自動で更新してくれるようにできんかな...


  • source ~/git_hub/ruby_docs/qiita/qiita_post/post_final.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gem paranoiaを使った論理削除

備忘録

今回自作アプリ作成の際に論理削除を用いたので備忘録として記録します。
プログラミング初心者のため間違いがあればご指摘お願いします!
今回はrails、MySQLを使い開発を進めています。

論理削除とは

一般的な削除は物理削除と言い削除したらDB上からもデータが削除されます。
それに対し、論理削除とは一般的な削除とは違い、あたかも削除されたように見せてDBにはデータが残っている状態のことを言います。
削除履歴を表示したり、データの復元などにも使われます。

ステップ1 Gemの導入

論理削除の手間を大幅にカットしてくれるGemのparanoiaを導入する。
Gemfileに記述しターミナルでbundle install

gem 'rails_12factor'

ステップ2 カラムを追加

論理削除をするとこちらのカラムに削除した日時が挿入され、削除フラグを立てる事ができます。

class AddDeletedAtToCategories < ActiveRecord::Migration[6.0]
  def change
    add_column :categories, :deleted_at, :datetime
  end
end

正しければ、$ rails db:migrateして下さい。
モデルに以下の記述をします。

ステップ3 モデルを編集する

acts_as_paranoid

まとめ

以上の設定でparanoiaを使った論理削除の設定が完了しました!
普通に実装しようとするとかなり大変だと思いますが、paranoiaを使う事で簡単に実装する事ができました!
プログラミング初心者の方の役に立てると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsガイド(Action Controller の概要)を改めて読んで

5. セッション

Railsアプリケーションにはユーザーごとにセッションオブジェクトが生成され、前のリクエスト情報を継続して利用することができる。セッションはControllerViewでのみ利用でき、少量のデータがセッションに保存される。

セッションの保存先としては、以下のようなストレージから選ぶことができる。

  • ActionDispatch::Session::CookieStore: すべてのセッションをクライアント側のブラウザのcookieに保存する
  • ActionDispatch::Session::CacheStore: データをRailsのキャッシュに保存する
  • ActionDispatch::Session::ActiveRecordStore: Active Recordを用いてデータベースに保存する (activerecord-session_store gemが必要)
  • ActionDispatch::Session::MemCacheStore: データをmemcachedクラスタに保存する (この実装は古いのでCacheStoreを検討すべき)

セッションは固有のIdを持っており、それをcookieに保存します。セッションIDをURLで渡すことはセキュリティ上とても危険があるため、Railsではデフォルトで許可されていない。なので、セッションIDは必ずcookieで渡さなくてはならない。

多くのSessionStore(のセッションID)は、サーバー上のセッションデータを検索するために使用される。
ただしCookieStoreだけは、cookie自身にすべてのセッションデータを保存する(必要であればセッションIDも利用可能)。

RailsではCookieStoreがデフォルトで使われ、Railsでの推奨セッションストアとなっている。

CookieStoreの利点は、

  • 非常に軽量な点
  • 新規で利用する時の準備が全く不要な点

cookieデータは改竄防止のために暗号署名が与えられており、cookie自身も暗号化されているため、内容を他人に読目内容になっている。(改竄されたcookieRailsが拒否してくれる)

CookieStoreには約4KBのデータを保存でき、容量はこれで十分。利用するセッションストアの種類に関わらず、セッションに大量のデータを保存することは好ましくない。

ユーザーセッションに重要なデータが含まれていない場合や、セッションを長期保存する必要のない場合はActionDispatch::Session::CacheStoreを検討するとよい。

メリットは、Webアプリケーションに設定されているキャッシュ実装を利用して保存するため、既存のキャッシュインフラをそのまま利用することができる。よって、特段準備する必要がない。

デメリットは、セッションが短命なので、セッションがいつでも消える可能性がある点である。

5-1. セッションにアクセスする

セッションへのアクセスは、コントローラ内のsessionインスタンスメソッドを使って可能となる。セッションの値は、ハッシュに似たキー/値ペアを使って保存される。

class ApplicationController < ActionController::Base

  private

  # キー付きのセッションに保存されたidでユーザーを検索する
  # :current_user_id はRailsアプリケーションでユーザーログインを扱う際の定番の方法。
  # ログインするとセッション値が設定され、
  # ログアウトするとセッション値が削除される。

<方法①>

  def current_user
    @current_user ||= session[:current_user_id] &&
      User.find_by(id: session[:current_user_id])
  end

<方法②>

  def current_user
    if session[:current_user_id]
      @current_user ||= User.find_by(id: session[:current_user_id])
    end
  end

end

セッションに何かを保存したければ、ハッシュのようにキーを割り当てる。

class LoginsController < ApplicationController
  # "Create" a login, aka "log the user in"
  def create
    if user = User.authenticate(params[:username], params[:password])
      # セッションのuser idを保存し、
      # 今後のリクエストで使えるようにする
      session[:current_user_id] = user.id
      redirect_to root_url
    end
  end
end

セッションからデータの一部を削除したい場合は、そのキーバリューペアを削除します。

class LoginsController < ApplicationController
  # ログインを削除する (=ログアウト)
  def destroy
    # セッションidからuser idを削除する
    session.delete(:current_user_id)
    # メモ化された現在のユーザーをクリアする
    @_current_user = nil
    redirect_to root_url
  end
end

セッション全体をリセットするにはreset_sessionを使う。

5-2. Flash

flashはセッションの中の特殊な部分で、リクエストごとにクリアされる。リクエスト直後のみ参照可能となる特徴を持っており、エラーメッセージをviewに渡したりする。

flashはハッシュとしてアクセスし、これをFlashHashインスタンスと呼ぶ。
例として、ログアウトする動作を扱ってみる。コントローラでflashを使うことで、次のリクエストで表示するメッセージを送信することができる。

class LoginsController < ApplicationController
  def destroy
    session.delete(:current_user_id)
    flash[:notice] = "You have successfully logged out."
    redirect_to root_url
  end
end

flashメッセージはリダイレクトの一部として割り当てることもできる。オプションとして:notice:alertの他に、一般的な:flashも使用可能。

redirect_to root_url, notice: "You have successfully logged out."
redirect_to root_url, alert: "You're stuck here!"
redirect_to root_url, flash: { referral_code: 1234 }

6. Cookie

Webアプリケーションでは、cookieと呼ばれる少量のデータをクライアント側(ブラウザ)に保存できる。HTTPは「ステートレス」なプロトコルなので、基本的にリクエストとリクエストの間には何の関連も持たないが、cookieを使うとリクエスト同士の間で (あるいはセッション同士の間であっても) このデータが保持されるようになる。Railsではcookiesメソッドを使ってcookieに簡単にアクセスできる。アクセス方法はセッションの場合とよく似ていて、ハッシュのように動作する。

class CommentsController < ApplicationController
  def new
    # cookieにコメント作者名が残っていたらフィールドに自動入力する
    @comment = Comment.new(author: cookies[:commenter_name])
  end

  def create
    @comment = Comment.new(params[:comment])
    if @comment.save
      flash[:notice] = "Thanks for your comment!"
      if params[:remember_name]
        # コメント作者名を保存する
        cookies[:commenter_name] = @comment.author
      else
        # コメント作者名がcookieに残っていたら削除する
        cookies.delete(:commenter_name)
      end
      redirect_to @comment.article
    else
      render action: "new"
    end
  end
end

セッションを削除する場合はキーにnilを指定することで削除しましたが、cookieを削除する場合はこの方法ではなく、cookies.delete(:key)を使用しなければならない。

Railsでは、機密データ保存のために署名済みcookie jarと暗号化cookie jarを利用することもできる。

  • 署名済みcookie jarでは、暗号化した署名をcookie値に追加することで、cookieの改竄を防ぐ。
  • 暗号化cookie jarでは、署名の追加と共に、値自体を暗号化してエンドユーザーに読まれることのないようにしてくれる。

これらの特殊なcookieではシリアライザを使って値を文字列に変換して保存し、読み込み時に逆変換(deserialize)を行ってRubyオブジェクトに戻している。

Rails.application.config.action_dispatch.cookies_serializer = :json

新規アプリケーションのデフォルトシリアライザは:jsonとなっている。

cookieには文字列や数字などの「単純なデータ」だけを保存することが推奨されている。cookieに複雑なオブジェクトを保存しなければならない場合は、後続のリクエストでcookieから値を読み出す場合の変換については自分で面倒を見る必要がある。

cookieセッションストアを使う場合、sessionflashハッシュについても同様のことが該当する。

7. XMLとJSONデータを出力する

ActionControllerのおかげで、XMLデータやJSONデータの出力 (レンダリング) は非常に簡単に行える。scaffoldを使って生成したコントローラは以下のようになっている。

class UsersController < ApplicationController
  def index
    @users = User.all
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render xml: @users }
      format.json { render json: @users }
    end
  end
end

上のコードでは、render xml: @users.to_xmlではなくrender xml: @usersとなっていることにご注目。Railsは、オブジェクトがString型でない場合は自動的にto_xmlを呼んでくれる。

.to_xml

配列またはハッシュをXML形式に変換するメソッド。

array.to_xml([オプション])
hassh.to_xml([オプション])

8. フィルタ

フィルタは、コントローラにあるアクションの「直前 (before)」、「直後 (after)」、あるいは「直前と直後の両方 (around)」に実行されるメソッド。

フィルタは継承されるので、フィルタをApplicationControllerで設定すればアプリケーションのすべてのコントローラでフィルタが有効となる。

「before系」フィルタのよくある使われ方として、ユーザーがアクションを実行する前にログインを要求するというのがあります。このフィルタメソッドは以下のようになる。

class ApplicationController < ActionController::Base
  before_action :require_login

  private

  def require_login
    unless logged_in?
      flash[:error] = "You must be logged in to access this section"
      redirect_to new_login_url # halts request cycle
    end
  end
end

このメソッドはエラーメッセージをflashに保存し、ユーザーがログインしていない場合にはログインフォームにリダイレクトするというシンプルなものである。

この例ではフィルタをApplicationControllerに追加したので、これを継承するすべてのコントローラが影響を受ける。つまり、アプリケーションのあらゆる機能についてログインが要求されることになる。当然だが、アプリケーションのあらゆる画面で認証を要求してしまうと、認証に必要なログイン画面まで表示できなくなるという困った事態になってしまうので、このようにすべてのコントローラやアクションでログイン要求を設定すべきではない。skip_before_actionメソッドを使えば、特定のアクションでフィルタをスキップすることが可能となる。

class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

上記のようにすることで、LoginsControllernewアクションとcreateアクションをこれまでどおり認証不要にすることができる。特定のアクションについてだけフィルタをスキップしたい場合には:onlyオプションを使うことができる。逆に特定のアクションについてだけフィルタをスキップしたくない場合は:exceptオプションを使う。これらのオプションはフィルタの追加時にも使うことができるので、最初の場所で選択したアクションに対してだけ実行されるフィルタを追加することもできる。

8-1. afterフィルタとaroundフィルタ

「before系」フィルタ以外に、アクションの実行後に実行されるフィルタや、実行前実行後の両方で実行されるフィルタを使うこともできる。

「after系」フィルタは「before系」フィルタと似ているが、「after系」フィルタの場合アクションは既に実行済みであり、クライアントに送信されようとしている応答データにアクセスできる点が「before系」フィルタとは異なる。当然ながら、「after系」フィルタをどのように書いても、アクションの実行が中断するようなことはない。ただし、「after系」フィルタは、アクションが成功した後にしか実行されず、リクエストサイクルの途中で例外が発生した場合は実行されないのでご注意する。

「around系」フィルタを使う場合は、フィルタ内のどこかで必ずyieldを実行して、関連付けられたアクションを実行してやる義務が生じます。これはRackミドルウェアの動作と似ている。

たとえば、何らかの変更に際して承認ワークフローがあるWebサイトを考えてみる。管理者はこれらの変更内容を簡単にプレビューし、トランザクション内で承認できるとする。

class ChangesController < ApplicationController
  around_action :wrap_in_transaction, only: :show

  private

  def wrap_in_transaction
    ActiveRecord::Base.transaction do
      begin
        yield
      ensure
        raise ActiveRecord::Rollback
      end
    end
  end
end

「around系」フィルタの場合、画面出力 (レンダリング) もその作業に含まれることにご注意する。特に上の例では、ビュー自身がデータベースから (スコープなどを使って) 読み出しを行うとすると、その読み出しはトランザクション内で行われ、データがプレビューに表示される。

あえてyieldを実行せず、自分でレスポンスをビルドするという方法もある。この場合、アクションは実行されない。

9. リクエストフォージェリからの保護

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git add .した後にgit statusするとChanges not staged for commit:と出る時の対処法

内容

GitHubにcommitしようと思いgit add . をしてgit status で確認したところ、"Changes not staged for commit:"と出てうまく反映されてなかった

実際出たエラー内容

$ git add .
$ git status
On branch login
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   app/controllers/users_controller.rb
    modified:   db/mysql/volumes/ib_logfile0
    modified:   db/mysql/volumes/ibdata1
    modified:   db/mysql/volumes/ibtmp1
    modified:   db/mysql/volumes/myapp_test/users.ibd
    modified:   db/mysql/volumes/mysql/innodb_index_stats.ibd
    modified:   db/mysql/volumes/mysql/innodb_table_stats.ibd
    modified:   spec/requests/users_signup_spec.rb

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   db/mysql/volumes/ibdata1

解決方法

このエラーの場合"db/mysql/volumes/ibdata1"がうまくステージされてないよと言われているので,ファイル名を指定してgit addコマンドを実行する

$ git add db/mysql/volumes/ibdata1
$ git status
On branch login
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   app/controllers/users_controller.rb
    modified:   db/mysql/volumes/ib_logfile0
    modified:   db/mysql/volumes/ibdata1
    modified:   db/mysql/volumes/ibtmp1
    modified:   db/mysql/volumes/myapp_test/users.ibd
    modified:   db/mysql/volumes/mysql/innodb_index_stats.ibd
    modified:   db/mysql/volumes/mysql/innodb_table_stats.ibd
    modified:   spec/requests/users_signup_spec.rb

ちゃんと反映されました!

最後に

コミットはこまめに行うので、エラーが出たら焦らず対処していきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

migrateでカラムに外部キーを追加する

プログラミング初心者でも理解できる内容を書いていければと思います。

railsでmigrateする際に、カラムにモデルの関連づけのために外部キーを追加する方法を整理します。

前提としては、ユーザーと、そのユーザーの複数の投稿(tweet)を紐付けたい、ということです。

◉外部キーをテーブルに追加する

そのためには、新しく作成するtweetsテーブルに user_id というカラムを追加して、投稿を所有しているユーザーの id を格納するようにします。

このような場合では、referenceというデータ型を使用して外部キーのカラムを追加します。

tweetsテーブルを作成するマイグレーションファイルを、こんな感じに記述します。

class CreateTweets < ActiveRecord::Migration[5.2]
 def change
   create_table :tweets do |t|
     t.string     :text,                     null: false
     t.references :user, foreign_key: true,  null: false

     t.timestamps
   end
 end
end

t.references :user, foreign_key: true ここですね。

t.referencees :user で、user_idというカラムを作成してくれます。そしてindexを自動で付与してくれます。indexにより検索が楽になります。

ただし、データの読み込みや取得が早くなるメリットがあるかわりに、書き込みの速度が遅くなるというデメリットがあるみたいです。

これだけだと、外部キー制約はつかないので、

foreign_key: true をつけます。

また、既存のテーブルに外部キーを追加したい場合は次のようになります。

class AddColumn < ActiveRecord::Migration[5.0]
 def change
   add_reference :tweets, :user, foreign_key: true
 end
end

ちなみに、、、

null: false と記述することで、NOT NULL制約をつけることができます。データベースカラムの値にNULLを格納したくない場合に、つまり、あるモデルの属性の値が必ず入らなければいけないと期待するときに、このような制約をつけます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】画面からアップロードされたXMLファイルをHash型で読み込む方法

Rails(5系)を使ったアプリケーションで、画面からアップロードされたXMLファイルをHash型で読み込みたい場合の実装例です。

アップロードするXMLファイル

今回は検証用に以下のXMLファイルを使用

<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item>AAA</item>
  <item>BBB</item>
  <item>CCC</item>
</items>

html.erbの記述

適当にファイルとボタンタグを配置します。(デザイン・スタイルはお好みでどうぞ!)

<%= form_tag xxx_path, multipart: true do %>
  <label>XMLファイルの読み込み</label>
  <div class="row">
    <div class="col-sm-2">
      <%= file_field_tag :file, class: 'btn btn-primary' %>
    </div>
  </div>
  <div class="row">
    <div class="col-sm-4">
      <%= submit_tag '送信', class: 'btn btn-primary' %>
    </div>
  </div>
<% end %>

Controller.rbの記述

xml = REXML::Document.new(File.new(params[:file].path).read)
xml_h = Hash.from_xml(xml.to_s)

上記の結果以下のようなHash形式でファイルの中身を取得できます。

{"items"=>{"item"=>["AAA", "BBB", "CCC"]}}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Active StrageをRSpecでテスト

はじめに

Active Strageを使用し、画像投稿ができるアプリを制作中です。
その際のテストの書き方の例を紹介します。

factories

FactoryBot.define do
  factory :post do
...
      trait :post_image do
        image {
          fixture_file_upload("app/assets/images/XXX.PNG")
        }
      end
...

letを定義

let(定義名) { 定義の内容 }

let(:post_image) { FactoryBot.create(:post_image) }

使用する(例)

post = FactoryBot.create(:post,:post_image)

参考にさせて頂いたサイト

https://qiita.com/maca12vel/items/ee4d16827f24f69080ae
https://shuttodev.hatenablog.com/entry/2019/09/04/015756

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] ActiveStorageを使ってpostsにアバターを表示

やりたいこと

Posts#index(以下タイムライン)にアバターやユーザー名を表示したい

前提

devise導入
ActiveStorage導入
アバターをusersモデルに追加

やり方

postsモデルに:avatarhas_one_attachedとuserインスタンスメソッドを追加

post.rb
class Post < ApplicationRecord
  validates :content, {presence: true, length: {maximum: 140}}
  validates :user_id, {presence: true}

  has_one_attached :avatar

  def user
    return User.find_by(id: self.user_id)
  end
end

viewに.userを追加

index.html.erb
    <% @posts.each do |post| %>
    <div class="posts-index-item mb-20">
      <div class="posts-index-user">
        <!--avatar-->
        <div class="posts-index-img d-inline">
          <% if post.user.avatar.attached? %>
            <%= image_tag post.user.avatar, class: "avatar-index rounded-circle mx-auto" %>
          <% else %>
            <img class="avatar-index rounded-circle mx-auto" src="<%= "/images/default_user.png" %>" alt="Userimage">
          <% end %>
        </div>
        <!--username-->
        <div class="posts-index-username d-inline">
          <%= link_to post.user.username, users_show_path %>
        </div>
      </div>
      <!--content-->
      <%= link_to(post.content, "/posts/#{post.id}") %>
    </div>
  <% end %>

結果

image.png

まとめ

userメソッドによってparamsの使えないposts#indexでもUserモデルを扱える。
post.user.avatarによりUserモデルに紐づいた:avatarを取り扱える

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データベースの操作(SQL編)

データベースの操作 ~SQL編~

  • SQLの使用方法を知る
  • データベースの基本的な操作を理解
  • テーブルの基本的な操作を理解

ターミナルでの使用方法

ターミナル
# ホームディレクトリに戻る
% cd

# MySQLに接続
% mysql -u root

Sequel Proでの使用方法

Sequel Proを使用してログインする際は、下記のように入力

9f4a2951092ddd67c27a8afcdc73d3d3.png

ターミナルで以下のSQLを実行

ターミナル
mysql> CREATE DATABASE sqltest;

MySQLで以下のSQL文を実行

ターミナル
mysql> SHOW DATABASES;

データベースを削除

ターミナル
mysql> DROP DATABASE sqltest;

データベースを指定

データベースを作成して、選択

ターミナル
mysql> CREATE DATABASE sqltest;
Query OK, 1 row affected (0.00 sec)

コマンドでテーブルを作成

ターミナル
mysql> CREATE TABLE goods (id INT, name VARCHAR(255));

作成したテーブルを表示

ターミナル
mysql> SHOW TABLES;

テーブルの構造を確認

ターミナル
mysql> SHOW columns FROM goods;

カラムを追加

ターミナル
mysql> ALTER TABLE goods ADD (price int, zaiko int);

カラムを変更

ターミナル
mysql> ALTER TABLE goods CHANGE zaiko stock int;

カラムを削除

ターミナル
mysql> ALTER TABLE goods DROP stock;

現場からは以上です!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む