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

carrierwaveでpdfファイル投稿機能をつける

はじめに

前回書いた記事でも触れたのですが、carrierwaveを用いてRailsアプリにpdf投稿機能をつけたのでその際に行った手順を備忘録として残しておきます。

carrierwaveの導入

gemfileに、下の1行を加えてbundle installします。

gemfile
gem 'carrierwave'

dbにfileカラムを追加

今回、送信されたデータをdbに保存するために、元々作っていたpostsテーブルにfileカラムを追加しました。マイグレーションファイルを作ってカラム追加しただけなのでコマンドは割愛します。

carrierwaveクラスの作成

carrierwaveを使うには、専用のクラスを作る必要があります。使い方の概要は以下の記事を参考にしました。
https://pikawaka.com/rails/carrierwave

以下、上記ページとほぼ同じではありますが自分が行った手順をまとめます。

bin/rails g コマンドでcarrierwaveクラスを生成

terminal
bundle exec rails g uploader アップローダー名

以下の場所 app/uploaders/アップローダー名_uploader.rb に作られた設定ファイルで次のように記述します。

app/uploaders/アップローダー名_uploader.rb
class アップローダークラス < CarrierWave::Uploader::Base
  #保存場所の設定
  storage :file

  #保存するフォルダの設定
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

今回はpublic配下のuploadsフォルダ内に投稿されたpdfファイルを保存します。デプロイの際は外部のストレージサービスへの指定にしたりもできるようです。

アップローダークラスとモデルの紐付け

クラスと関連づけたいモデルで、以下の記述をします。

モデル名.rb
class モデル名 < ActiveRecord::Base
  mount_uploader :関連付けたいカラム名, アップローダークラス
end

フォームにfile_fieldを追加&dbに保存

パラメーターでfile_fieldの情報をfileカラムに保存するようにしました。(保存場所はご自分の任意の保存したいカラムで構いません)コントローラーでいつも通り記述するだけなので割愛します。

viewで表示する

viewで次の記述をします。

app/views/任意のview
<object data="<%= @post.file.url %>" type="application/pdf" width="200" height="300"></object>

@postはこのviewをレンダリングするアクションで定義したpostモデルのインスタンス変数で、fileというカラムを持っています。そこに、carrierwaveで生成したアップローダークラスが持つurlメソッドを用いて、ファイルまでのurlを指定しています。(自分で設定したモデル、カラム名に適宜置き換えてください。) width, height で表示するサムネイルのサイズを指定できます。

以上で表示できました!

終わりに

サムネイル生成の方法はobjectを使うもの以外もあるようなので気になる方は調べてみるとよいかもしれません。僕はサムネイルがなかなか実装できずcarrierwaveのエラーだと思っていたらform_withの指定ミスだったので、もし詰まっている方はそちらも確認してみるとよいかもです。

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

RubyなリポジトリのGitHub ActionsでCoverallsを使う

はじめに

Travis CIからGitHub Actionsに移行する際に、GitHub ActionsでSimpleCovの結果をCoverallsに送信する方法を調べました。simplecov-lcovを使用して、code coverageをlcov形式で出力し、それをCoverallsに送るという感じでした。

SimpleCovとsimplecov-lcovのインストール

Gemfileに以下の二行を追加して、bundle install しました。

gem 'simplecov', '~> 0.21'
gem 'simplecov-lcov', '~> 0.8'

spec/spec_helper.rb の先頭に以下を追加します。Coverallsにcoverageを送る場合に重要になるのは、SimpleCov::Formatter::LcovFormatter に関係する部分です。

require 'simplecov'
require 'simplecov-lcov'

SimpleCov::Formatter::LcovFormatter.config do |config|
  # Coverallsはデフォルトではcoverage/lcov.infoの結果を送信する
  config.report_with_single_file = true
  config.single_report_path = 'coverage/lcov.info'
end

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
  SimpleCov::Formatter::HTMLFormatter, 
  # formatterにSimpleCov::Formatter::LcovFormatterを加える
  SimpleCov::Formatter::LcovFormatter
])

SimpleCov.start

Coveralls GitHub Actions

https://github.com/marketplace/actions/coveralls-github-action に、CoverallsのGitHub Actionsがあります。
coveralls_github_actions-fs8.png

Use latent version ボタンを押すと表示されるモーダルの二行をコピーして、Workflowのymlに追記します。

coveralls_github_actions_code-fs8.png

Workflowの例

code coverageをCoverallsに送信するために .github/workflows/coverage.yml というworkflowファイルを用意しました。重要なのは、前節でコピーした最後の - name: Coveralls GitHub Action からの部分です。github-token は必須のオプションになりますが ${{ secrets.GITHUB_TOKEN }} としておけばOKです。

name: coverage

on: [push, pull_request]

jobs:
  coverage:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Set up Ruby 2.7
        uses: actions/setup-ruby@v1
        with:
          ruby-version: '2.7'
      - name: Build and test with Rake
        run: |
          gem install bundler
          bundle install
          bundle exec rake
      - name: Coveralls GitHub Action
        uses: coverallsapp/github-action@v1.1.2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

必要かどうかちょっと謎ですが、一行だけの .coveralls.yml を用意しました。

service_name: github-ci

あとはこれらをcommitしてpushすれば、workflowが動きだして、Coverallsに結果が送られます。あらかじめ、Coveralls側でリポジトリの連携をONにしておいてください。

おわりに

CoverallsのGitHub Actionのexampleが、Node.jsのものだったので、Rubyは対応してないのかな〜と思っていたのですが、ちゃんと調べたら簡単でした。Travis CIの頃は、coveralls gemを使っていました。coveralls gemでは、SimpleCovのバージョンにしばりがあるので、今回の様なsimplecov-lcovを使った方法の方が自由で良いですね。

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

[ruby]ヒアドキュメントについて

*追記
”実はputsやprint意外にも文字列を出力する方法があります
それがヒアドキュメントです
しかもただ単に出力するのではなく、空白や改行も含める長い文章が出力できる様になります
rubyだと改行した瞬間文の終了とみなされますがヒアドキュメントを利用すると・・・”
とご指摘があり、間違った解釈をしておりました
ありがとうございます

ヒアドキュメント

長文を識別子で囲む事によって長文も保存できる様になれる

irb(main):033:0> puts = <<hia
irb(main):034:0" ruby
irb(main):035:0" most like
irb(main):036:0" Programming language
irb(main):037:0" hia
=> "ruby\nmost like\nProgramming language\n"

解説しますとまず
1行目にputsと言う変数にhiaと言う識別子で開始ラベル
4行目で終了ラベルである識別子を入れてあげると2~3行目の内容を変数putsに格納してくれる
6行目には格納された内容が表示される

irb(main):038:0> puts puts
ruby
most like
Programming language
=> nil

もちろんputs 変数(puts)とすると先ほど変数に格納された文字列を出力できる

ちなみに変数に文字列や数字を前もって代入した状態でもヒアドキュメントは式展開で出力できる

irb(main):059:0> name = "タケシ"
=> "タケシ"
irb(main):060:0> age = 12
=> 12
irb(main):062:0> city = "神奈川"
=> "神奈川"
irb(main):063:0> text = <<EOF
irb(main):064:0" 私の名前は#{name}です
irb(main):065:0" 年齢は#{age}
irb(main):066:0" 出身は#{city}
irb(main):067:0" EOF
=> "私の名前はタケシです\n年齢は12\n出身は神奈川\n"
irb(main):069:0> puts text
私の名前はタケシです
年齢は12
出身は神奈川

と今回はヒアドキュメントについて勉強しアウトプットしました

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

formオブジェクトでタグ付け機能を実装

実装の流れ


tagモデルと中間テーブルを作成

投稿とタグは多対多の関係になるため、中間テーブルを作成します。

投稿のモデルを作成します。

$ rails g model item name:string user:references

タグモデルを作成します。

$ rails g model tag word:string

中間テーブルを作成します。

$ rails g model item_tag_relation item:references tag:references 

マイグレーションファイルを確認します。

db/migrate/create_items.rb
class CreateItems < ActiveRecord::Migration[6.0]
  def change
    create_table :items do |t|
      t.string :name
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end
db/migrate/create_tags.rb
class CreateTags < ActiveRecord::Migration[6.0]
  def change
    create_table :tags do |t|
      t.string :word
      t.timestamps
    end
  end
end
db/migrate/create_item_tag_relations.rb
class CreateItemTagRelations < ActiveRecord::Migration[6.0]
  def change
    create_table :item_tag_relations do |t|
      t.references :item, foreign_key: true
      t.references :tag, foreign_key: true
      t.timestamps
    end
  end
end

unique: trueで同じタグ名を登録しないようにできますが、うまく実装できなかったので別の方法で一意性を持たせました。

$ rails db:migrate

モデルの関連付けとバリデーション

app/models/item.rb
class Item < ApplicationRecord
  has_many :item_tag_relations
  has_many :tags, through: :item_tag_relations, dependent: :destroy

  belongs_to :user
end

「has_many :tags, through: :item_tag_relations」の記述にによって、item_tag_relationsモデルを通してアイテムに紐づくタグを取得します。

app/models/tag.rb
class Tag < ApplicationRecord
  has_many :item_tag_relations, dependent: :destroy
  has_many :items, through: :item_tag_relations

  validates :word, uniqueness: true
end

「validates :word, uniqueness: true」の記述によって、タグの名前が重複して登録されるの防ぎます。(何故かうまく働きませんでした...)

app/models/item_tag_relation.rb
class ItemTagRelation < ApplicationRecord
  belongs_to :item
  belongs_to :tag
end

ルーティングを設定

config/routes.rb
Rails.application.routes.draw do
 root to: 'items#index'
 resources :items
end

formオブジェクトの作成

Formオブジェクトは、1つのフォーム送信で複数のモデルを更新するときに使用するツールです。自分で定義したクラスをモデルのように扱うことができます。

modelsディレクトリ配下に「app/models/item_tag.rb」ファイルを作成します。

app/models/item_tag.rb
class ItemTag
  include ActiveModel::Model
  attr_accessor :name, :user_id, :item_id, :tag_ids
end

 ActiveModel::Modelをincludeすることで、そのクラスのインスタンスはActiveRecordを継承したクラスのインスタンスと同様に form_with や render などのヘルパーメソッドの引数として扱えたり、バリデーションの機能が使えるようになります。

 attr_accessorで使用したいカラム名をセットします。

 続いて、フォームからパラメーターとして送られてきた情報をテーブルに保存する処理を追加します。

app/models/item_tag.rb
class ItemTag
  include ActiveModel::Model
  attr_accessor :name, :user_id, :item_id, :tag_ids

  def save
    @item = Item.create(name: name, user_id: user_id)
    tag_list = tag_ids.split(/[[:blank:]]+/).select(&:present?)
    tag_list.each do |tag_name|
      @tag = Tag.where(word: tag_name).first_or_initialize
      @tag.save
      unless ItemTagRelation.where(item_id: @item.id,tag_id: @tag.id).exists?
        ItemTagRelation.create(item_id: @item.id, tag_id: @tag.id)
      end
    end
  end
end

 アイテムの情報を保存し「@item」という変数に代入しています。


 tag_list = tag_ids.split(/[[:blank:]]+/).select(&:present?)は入力フォームのf.text_fieldから送られたタグをtag_idsとしてparamsで送信します。

 そして、split(/[[:blank:]]+/)によってtag_ids内の文字列を空白で区切り、バラバラの単語にして配列に入れていきます。
 
 最後に、select(&:present?)は、配列化した値をそれぞれpresent?メソッドで判定して、真であれば取り出します。


@tag = Tag.where(word: tag_name).first_or_initializeで新規タグか既存タグかの判別をします。

判別をして既存タグなら既存のidを使用。新規ならidを生成します。


 unless ItemTagRelation.where(item_id: @item.id,tag_id: @tag.id).exists?は今回重複したタグを保存できないようにしたかったのですが、バリデーションやマイグレーションファイルに一意性を持たせても保存されてしまったので、苦肉の策でモデルにて対処しました。

 ItemTagRelation.where(item_id: @item.id,tag_id: @tag.id).exists?で、中間テーブルであるitem_tag_relationモデルの投稿に対して、同じ名前のタグが存在していないかを.exists?で判定しています。
 
 タグが重複した場合はtureになるのでunlessで条件式をかけています。


controllerの処理

$ rails g controller items
app/controllers/items_controller.rb
  def new
    @item = ItemTag.new
  end

  def create
    @item = ItemTag.new(itemtags_params)
    if @item.valid?
      @item.save
      redirect_to items_path(@item)
    else
      render :new
    end
  end

 private

  def itemtags_params
    params.require(:item_tag).permit(:name, :text, :image, :tag_ids).merge(user_id: current_user.id)
  end

formオブジェクトに対してnewメソッドを使用しています。


viewの作成

関連する所のみ記述します。

app/views/items/new.html.erb
<%= form_with model: @item, url: items_path, local: true do |f| %>
  <%= f.text_field :tag_ids %>
<%= f.submit "投稿する" %>

tag_idsはitemモデルで「 has_many :tags, through: :item_tag_relations」の関連付けをすることよってアイテムオブジェクトに使用できるようになります。

ひとまずこれで、タグ付け機能を実装できます。

参考にさせていただいた記事

【Ruby on Rails】タグ検索機能を実装してみた
https://qiita.com/E6YOteYPzmFGfOD/items/177f18e706df05f9b42e

初心者が手探りで Rails のタグ付機能を gem なしで実装してみる
https://qiita.com/ryutaro9595/items/042a1ec713c8c1f2c1d6

rails 投稿記事にタグをつける機能を実装する。
https://shirohige3.hatenablog.com/entry/2020/11/08/013327

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

【CircleCI2.0】JavaScript に対応するように設定

はじめに

CircleCI で JavaScript を扱えるように設定しました。
今まで問題なく動作していた CircleCI のテストでしたが、あることをしてから通らなくなったので対処方法を記していきます。

今までの設定ファイルはこちらです。

目次

  • 環境

  • 問題

  • 原因

    • RSpecでjs: trueを使用
    • 今回 ローカル環境CircleCI環境 をあわせるために行った変更
  • 解決策

    • spec/support/capybara.rb
      • 変更前
      • 変更後
    • spec/rails_helper.rb
    • Dockerfile
    • .circleci/config.yml
    • .circleci/config.yml 全体
    • config/database.yml.ci
  • まとめ

  • おわりに

  • 参考文献

環境

  • Ruby: 2.6.6
  • Bundler: 2.0.2
  • MySQL: 8.0
  • CircleCI: 2.0

問題

SocketError: 
Failed to open TCP connection to chrome:4444 
(getaddrinfo: No address associated with hostname)
# chrome:4444へのTCP接続のオープンに失敗
SocketError: getaddrinfo: No address associated with hostname
# ホスト名に関連付けられたアドレスはありません

原因

RSpecでjs: trueを使用

RSpecで JavaScriptのテストを実装 したところローカル環境で通っていたテストがCircleCIの自動テストでは通りませんでした。
結論として、ローカル環境とCircleCIの環境の差異が原因かと思います。
ローカルではchromeコンテナを立ち上げて System Spec を通していましたが、 CircleCI の設定ファイル でも設定が必要なのでエラーが起きていました。

今回ローカル環境とCircleCI環境をあわせるために行った変更

  • chromeコンテナ から Dockerfileで chrome を追加するようにしたこと
  • Chrome使用時のURLを指定していたので削除したこと
  • CircleCI の設定ファイルChrome の追加background で使用するようにしたこと

解決策

spec/support/capybara.rb

大幅に変更したように見えますが、url を指定していないというのがポイントです。

変更前
require 'capybara/rspec'
require 'selenium-webdriver'

module CapybaraSupport
  Capybara.javascript_driver = :selenium_chrome_headless
  Capybara.default_driver    = :selenium_chrome_headless
  Capybara.register_driver :selenium_chrome_headless do |app|
    url = 'http://chrome:4444/wd/hub'
    caps = ::Selenium::WebDriver::Remote::Capabilities.chrome(
      'goog:chromeOptions' => {
        'args' => [
          'no-sandbox',
          'headless',
          'disable-gpu',
          'window-size=1680,1050'
        ]
      }
    )
    Capybara::Selenium::Driver.new(app, browser: :chrome, url: url, desired_capabilities: caps)
  end
変更後
require 'capybara/rspec'
require 'selenium-webdriver'

Capybara.register_driver :selenium_chrome_headless do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-dev-shm-usage')
  options.add_argument('--window-size=1400,1400')

  driver = Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = :selenium_chrome_headless

spec/rails_helper.rb

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :selenium_chrome_headless
  end
end

Dockerfile

ローカル環境のテストで Chrome を使用したいので DockerfileChromeの追加 をしています。
この記述を RUN mkdir /myapp の前に書きました。

RUN apt-get update && apt-get install -y unzip && \
    CHROME_DRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
    wget -N http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \
    unzip ~/chromedriver_linux64.zip -d ~/ && \
    rm ~/chromedriver_linux64.zip && \
    chown root:root ~/chromedriver && \
    chmod 755 ~/chromedriver && \
    mv ~/chromedriver /usr/bin/chromedriver && \
    sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' && \
    sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \
    apt-get update && apt-get install -y google-chrome-stable

# ルート直下にmyappという名前で作業ディレクトリを作成(コンテナ内のアプリケーションディレクトリ)
RUN mkdir /myapp
WORKDIR /myapp

.circleci/config.yml

CircleCIの設定ファイル。Chromeを追加 することで ローカル環境との差異 をなくすようにしました。

    steps:
      - run:
          name: Chrome Driver Install
          command: |
            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 -y unzip
            wget -N http://chromedriver.storage.googleapis.com/87.0.4280.88/chromedriver_linux64.zip -P ~/
            unzip ~/chromedriver_linux64.zip -d ~/
            rm ~/chromedriver_linux64.zip
            sudo chown root:root ~/chromedriver
            sudo chmod 755 ~/chromedriver
            sudo mv ~/chromedriver /usr/bin/chromedriver
            sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -'
            sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
            sudo apt-get update && sudo apt-get install -y google-chrome-stable
          background: true

.circleci/config.yml 全体

RSpecを動かすところで no_output_timeout: 15m と記述しています。
こちらはなくともテストには直接関係ないので削除しても大丈夫です。

なにも設定しなければ標準で10分の制限時間があり、それを過ぎるとタイムアウトエラーになってしまうので念の為付けております。テストの数とかにもよると思いますが、実際にはほんの数分で終わるので不要です。

version: 2

jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.6-node-browsers
        environment:
          - BUNDLER_VERSION: 2.0.2
          - RAILS_ENV: 'test'
      - image: circleci/mysql:8.0
        command: [--default-authentication-plugin=mysql_native_password]
        environment:
          - MYSQL_USER: root
          - MYSQL_DB: ci_test
          - MYSQL_ROOT_HOST: "127.0.0.1"

    working_directory: ~/myapp

    steps:
      - run:
          name: Chrome Driver Install
          command: |
            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 -y unzip
            wget -N http://chromedriver.storage.googleapis.com/87.0.4280.88/chromedriver_linux64.zip -P ~/
            unzip ~/chromedriver_linux64.zip -d ~/
            rm ~/chromedriver_linux64.zip
            sudo chown root:root ~/chromedriver
            sudo chmod 755 ~/chromedriver
            sudo mv ~/chromedriver /usr/bin/chromedriver
            sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -'
            sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
            sudo apt-get update && sudo apt-get install -y google-chrome-stable
          background: true

      - checkout

      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            - v1-dependencies-

      - run:
          name: install dependencies
          command: |
            gem install bundler -v 2.0.2
            bundle install --jobs=4 --retry=3 --path vendor/bundle

      - save_cache:
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - ./vendor/bundle

      - run: mv config/database.yml.ci config/database.yml
      - run: yarn install
      - run: bundle exec rake db:create
      - run: bundle exec rake db:schema:load

      - run:
          name: run tests
          command: |
            mkdir /tmp/test-results
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
              circleci tests split --split-by=timings)"
            bundle exec rspec \
              --format RspecJunitFormatter \
              --out /tmp/test-results/rspec.xml \
              --format progress \
              $TEST_FILES
          no_output_timeout: 15m

      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results

config/database.yml.ci

test:
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: 'root'
  port: 3306
  host: '127.0.0.1'
  database: ci_test

まとめ

  • DockerfileCircleCIの設定ファイルChromeを追加
  • ローカルで通用してもCircleCI上で失敗することがある。
  • ありがたいことに記事(情報)がたくさんあるので、その中で自分がどの問題に直面しているのかを理解することが大事。

おわりに

前回の記事に変更前の CircleCIの設定ファイル があります。(厳密に言えば少しリファクタなどしています)
JavaScript適用前 ですが、 CircleCI についてまとめていますのでよろしければご覧ください。

参考文献

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

Rubyメモ

Ruby‐ハッシュについて‐

変数名={シンボル:値1,シンボル:値2}
puts 変数名[:シンボル]

qiita.rb
cat = {name: "siro", age: 23}
put cat[:name]

jsと似ているが出力の表記が違うので注意!:smile:

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

【Ruby】配列の要素を交互に取得する。

目的

配列の要素を交互に取得し、二つの配列を作成します。
色々なやり方があるとは思いますが、私はこのような方法をとりました。

方法

array = ['い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を']

array_A = []
array_B = []

array.each_with_index{|kana, index| index.even? ? array_A << kana : array_B << kana}

p array_A
# => ["い", "は", "ほ", "と", "り", "る"]
p array_B
# => ["ろ", "に", "へ", "ち", "ぬ", "を"]

使用したメソッド

メソッド 意味
each_with_index 各要素とそれに付随する添字(インデックス)に対し処理を繰り返す
even? 要素が偶数であればtrueを返す
? : 条件演算子(三項演算子) if・else文を1行で書いたもの
<< 配列に要素を追加する

解説

まず、空の配列を準備します。

array_A = []
array_B = []

この中に元の配列arrayから交互に要素を入れていきます。
そのために使ったメソッドがeach_with_indexです。

each_with_index

このメソッドは繰り返しを行うeachメソッドに変数として各要素のインデックスを使用できるようにしたものです。

['い', 'ろ', 'は'].each_with_index{|kana, index| p "#{kana} - #{index}"}
# => "い - 0"
#    "ろ - 1"
#    "は - 2"

配列arrayの各要素とインデックスは,

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

となっているため、これを利用して、インデックスが偶数の時にはarray_Aに、奇数の時にはarray_Bに要素を入れていきます。

条件分岐には条件演算子を使用しました。

? :

?の前の条件で分岐し、:の前後で、trueの場合とfalseの場合それぞれの処理を分けています。

num = 1

num == 1 ? "これは 1 です" : "これは 1 ではありません"
# => "これは 1 です"

これは、

num = 1

if num == 1
  "これは 1 です"
else
  "これは 1 ではありません"
end
# => "これは 1 です"

と同じ意味になります。

そして、条件の偶数であるかどうかを確認するため、even?メソッドを使用しました。

even?

このメソッドはレシーバ(.の左側)が偶数であればtrueを、奇数であればfalseを返してくれます。

2.even?
# => true

1.even?
# => false

# 0も偶数です
0.even?
# => true

ちなみに、演算子を使用した計算でも同じことができます。

num = 2

num % 2 == 0
# => true

ある数を2で割った時、余りが0であれば偶数です。

even?でインデックスが偶数か奇数かを確認し、? :で条件分岐したあとは、
<<で配列に要素を足しています。

<<

<<は配列の末端に要素を一つ追加します。

num_array = [1, 2]

num_array << 3

p num_array
# => [1, 2, 3]

最後に

さらに良い方法がございましたら、ご教示いただけますと幸です。
ご覧いただき、ありがとうございました。

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

Deviceの導入に関してのまとめ

※アプリケーション作成からDeviseの導入までをメモがわりに記入しています。

前提

ruby 6.0.0
rails 6.0.3.4
mysql2 0.5.3
devise 4.7.3

上記の環境で作成する。

簡易的な流れ

①作りたいアプリケーションを立ち上げます。

コンソール
% rails _6.0.0_ new アプリ名 -d mysql

②アプリ内のdatabase.ymlの記述を書き換えます。

config/database.yml
#中略
default: &default
  adapter: mysql2
# ↓↓元々はencoding: utf8mb4
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

#以下略

③gemfile内の最下層に追加したいgemを記入します。

Gemfile
#中略
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

gem 'pry-rails'
gem 'devise'

④gemを読み込ませます。

コンソール
% bundle install
コンソール
% yarn install

⑤コンソール上の作成したアプリのディレクトリでデータベースを作成します。

コンソール
% rails db:create

⑥アプリにDeviseをインストールします。

コンソール
% rails g devise:install

⑦Deviseを用いたモデルの生成をします。
 (マイグレーションファイルやルーティングも自動生成されます。)

コンソール
# モデル名は単数
% rails g devise モデル名

⑧新規登録とログインのためのビューファイルを作成します。

コンソール
% rails g devise:views

⑨ローカルサーバーを起動します。

コンソール
% rails s

⑩マイグレーションファイルに追加するカラムを記述します。
※emailとpasswordについてはdeviseで生成した時に作成されています。

11.テーブルをマイグレーションします。

コンソール
% rails db:migrate

12.各編集・設定を行います。
view/devise/registrations →新規登録画面の編集
view/devise/sessions  →ログイン画面の編集
model/テーブル名.rb  →バリデーションの設定

13.application_controller.rbを編集します。

controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters
# keysの内容はdeviseのテーブルに追加したカラム名
    devise_parameter_sanitizer.permit(:sign_up, keys: [:カラム1, :カラム2, ])
  end
end

以上

その他注意点など

・新規登録ページ、ログインページに至るためのルーティングは、モデル作成時に自動で生成されています。

・ユーザー管理機能に必要なコントローラーの処理は裏側に隠れているため、コントローラーを統括している「application_controller.rb」に対して記述することになります。

・「rails g devise:install」がうまくいかない時は、以下を実行してみましょう。

コンソール
$ bundle exec spring stop
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby色々なメソッド(数値編)

始めに

皆さん、こんにちは
私は某スクールに通っており現在転職活動中です

rubyのメソッドやクラス、オブジェクト指向について不足しているなあと感じ今一度復習の為勉強しております
今回は数値クラスのメソッドについてまとめました

数値クラスのメソッド

※変数(num)には2.9を代入しております

  • 変数.round・・・四捨五入して整数にする
irb(main):005:0> num.round
=> 3
  • 変数.floor・・・切り下げ(て整数に変換)
irb(main):006:0> num.floor
=> 2
  • 変数.ceil・・・切り上げ(て整数に変換)
irb(main):007:0> num.ceil
=> 3
  • 変数.truncate・・・小数点の切り捨て
irb(main):008:0> num.truncate
=> 2

判定

特定の判定なのでtrueかfalseで判定します
メソッドの後ろに?がつくと真偽値を返してくれるメソッドになります

  • 変数.zero?・・・数値がゼロかどうかの判定
#変数(num)に0が代入されていた場合
irb(main):010:0> num = 0
=> 0
irb(main):012:0> num.zero?
=> true
#変数(num)に1が代入された場合
irb(main):013:0> num = 1
=> 1
irb(main):014:0> num.zero?
=> false
  • 変数.odd?・・・数値が奇数かどうか?
#変数(num)に1が代入された場合
irb(main):016:0> num.odd?
=> true
  • 変数.even?・・・数値が偶数かどうか?
#変数(num)に1が代入された場合
irb(main):017:0> num.even?
=> false

最後に

ぶっちゃけ判定以外使った事がなかった・・・?
今後使うかどうかわからないけど、頭には入れておいた方が何かの役に立つから予習、復習はかかさずにしようね

次回は文字クラス(string)について書きます!

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

ActiveRecordでwhere in句を書こうとして詰まったこと

SQLで書くとわかるが、Railsでどのように書くか悩んだので、書いておく

知りたかったこと

SELECT * FROM Tables WHERE (name, task) not in ((yamada, "running"),(yamada, "swimming"));

いろいろ悩んだが上手くまとめられなかったので、サブクエリで解決しました〜!
配列で渡すと複数個渡せますし、このようにサブクエリにすると欲しい条件が作れるのかなと思います!

期待値通りのクエリが発行
Table.where.not(id: (Table.select(:id).where(name: yamada).where(task: ["running", "swimming"])))

当初の方針

最初の方針ではwhere句を複数繰り返すことで条件を絞れると思ってましたが、以下のように書いたら"yamada"さんの全てのtaskを弾いてしまいます。実際に発行されるSQLも下に記載しておきます。
ベン図を頭の中に描きながら書いていきました。

期待値と違うクエリが発行
Table.where.not(name: yamada).where.not(task: ["running", "swimming"])
期待値と違うクエリ
SELECT `tables`.* FROM `tables` WHERE `tables`.`deleted_at` IS NULL AND (`tables`.`name` != "yamada") AND (`tables`.`task` NOT IN ("running", "swimming"))

参考文献

Rails ガイド

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

【初学者】rails6アプリにCDNを使わずにbootstrapを導入しようとして学んだ事【アセットパイプライン】

はじめに

ポートフォリオのためにrails6でアプリケーションを作っていたが、気軽に綺麗なフロントが欲しかったためBootstrapを導入したかった。
以前は完全にCSSだけで作られていたCSSフレイムワークであるBULMAを使っていた。
CDNを使って導入するのではなく、gemを使って導入したいと思いました。

事前知識

Bootstrapをgemで導入するには、大きく以下の3つの要素が必要。
- bootstrap (gem)
- popper.js (bootstrap (gem)に含まれている)
- jquery-rails (gem)
導入は以下の公式文章を元に実施しました。
bootstrapの導入方法(公式文章)

結論

アセットパイプラインを学んだ。
bootstrapをrails6に導入しようとする場合Sprocketというアセットパイプラインを使う必要があるが、
rails6のデフォルトのアセットパイプラインはWebpackerであり公式文章通りに手順を進めても上手く動くはずがなかった。
bootstrapのCSSの要素のみを使う場合は動いたが、jqueryだったりpopper.jsに依存する要素は正常に作動しなかった。(プルダウン等)

問題

結論でもう書きましたが、アセットパイプラインという存在を知らずにエラーが発生しました。

アセットパイプラインとは

アセットパイプラインとは、JavaScriptやCSSのアセットを最小化 (minify: スペースや改行を詰めるなど) または圧縮して連結するためのフレームワークです。(参照:railsガイド

つまり、アプリケーションのフロントを装飾するファイル(image, CSS, JS, etc)を全て集めてきて合体させて圧縮してくれる機能を持ったものです。
集めて合体して圧縮してくれるのは、ファイルへのリクエストの回数を減らして処理をなるべく少なくするためです。

Sprocketsとは

Rails 3.1 から導入された gem で、アセットファイル(JS, CSS, 画像など)を効率的に管理するための Asset Pipeline という仕組みの基盤です。
主な機能は以下のような物があります。

  • アセットファイルのパス管理
  • アセットファイルの結合・圧縮
  • アセットファイルの依存関係の解決
  • アセットファイルのコンパイル
  • 最終的に出力するファイルに hash を付けてキャッシュを無効化

などなど。(参照Rails Sprocketsとのお別れの仕方 - 最初の一歩 -)

印象としては古くて一部のユーザーにとっては、ファイルの管理方法に問題があるような印象で動作が遅いという事でした。

Webpackerとは

まず前提として、webpackとwebpackerは別物です。
webpackはJSのnpmのパッケージです。JSのコミュニティの中で育ちました。npmというのは、Node.jsで使えるパッケージ管理ツールのことで、つまりはRubyでいうbundlerです。JSの開発者たちは、このnpmか、その代替のyarnをみんな使っています。
webpackerはRubyのgemです。Railsでもwebpackが楽に使えるように作られました。

webpackはモジュールハンドラーです。
モジュールバンドラーとはモジュールをひとまとめに、つまりバンドルしてくれるためのものです。
(参照:全Rubyistに今すぐ伝えたいwebpackとwebpacker)

参考記事

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

form_with関連でつまずいたことのまとめ

はじめに

Railsチュートリアル(version5.1対応)を1周して、ポートフォリオ作りをする際に、form_withでいくつか詰まったので備忘録として記事を書きます。間違っているところがあればコメントいただけるとありがたいです。

以下発生したエラーごとにまとめていきます。

flashが表示されない

以下のような記事を投稿するフォームを作成

_post_form.html.erb
<%= form_with model: @post, url: yield(:form_url), method: yield(:method),
  local: true, multipart: true do |form| %>

・・・フォームいろいろ

<% end %>

初め、local: true の設定を行わずに書いていたのですが、投稿成功のflashの表示ができなくなって困っていました。どうやら、form_withはデフォルトではAjaxを用いて通信を行うようで、それによりflashが表示できないようです。(詳しい原理理解はしていません)

ファイルの送信が行えない

これは以下のようにpdfファイルを送信するフォームを作る際に起こりました。

_post_form.html.erb
<%= form_with model: @post, url: yield(:form_url), method: yield(:method),
  local: true, multipart: true do |form| %>

・・・他のフォーム・・・

<%= form.label :file, "pdfファイル", class: "control-label" %>
<%= form.file_field :file, class: "form-control-file" %>

<% end %>

pdfファイルの送信には、carrierwave を導入しました。carrierwaveのセッティングについては別記事にまとめます。今回は、それが終わった後の話です。
フォームが送れないのでググったところ、form_withの引数に、multipart: trueを指定していませんでした。これを指定すると、ファイルの形式も送信できるようになるようです。Rails4以降ではfile_fieldの記述があれば暗黙にmultipart: trueが指定される、と書いていたのですが、なぜか僕はされていませんでした・・・。

scopeオプションについて

以下の記事を参照しました。
https://qiita.com/akilax/items/f36b13f377f7e442bc73

データをパラメーターとして送る際に strong parameters として送るために指定する値、と理解しました。(正しいのか・・・?)

終わりに

まだ理解が浅いので、今後新しい発見があり次第追記していくつもりです。form_with 奥が深いな・・・。

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

テーブルが存在するのに、Table doesn't existと言われた話

はじめに

先日、PCの電源を閉じた後、再度dockerを起動し、docker-compose run wen rails sと入力すると、以下のようなエラーが出てしまいました。

Mysql2::Error: Table 'share_read_development.books' doesn't exist

現在でも、主原因は解明できておりません。
本記事では、僕が解決に向けて試したことを記載していきます。

エラーが起きた主原因は不明でしたが、エラーの解決は済んでおり開発自体は進んでおります。
もし同じようなエラーに苦しんでいる方は、参考にしてみてください。

また原因がわかる方がいらっしゃいましたら、コメントで教えていただけると幸いです。

開発環境

Ruby 2.72
Rails 6.0.2.3
MySQL 5.7
Docker/docker-compose

発生したエラー

冒頭でも簡単に記載しましたが、以下のようなエラーが出ました。
booksテーブルがないと言われています。

rails aborted!
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'share_read_development.books' doesn't exist
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `block in query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `handle_interrupt'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `query'
/usr/local/bundle/gems/activerecord-6.0.3.4/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:201:in `block (2 levels) in execute'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'

# 中略

Caused by:
Mysql2::Error: Table 'share_read_development.books' doesn't exist
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `block in query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `handle_interrupt'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `query'

# 中略

/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.3.4/lib/active_support/dependencies.rb:324:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:schema:dump
(See full trace by running task with --trace)

試したこと

マイグレーションの実行状況の確認
まず、マイグレーションの実行状況を確認しました。

$ docker-compose run web rails db:migrate:status

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20201207135139  Create users
   up     20201213135207  Create reviews
   up     20201218052640  Create books
   up     20201218075413  Add bookgenreid to books
   up     20201220063200  Add book to reviews
   up     20201222080803  Create relationships
   up     20201222131717  Create favorites
   up     20201222233733  Create comments
   up     20201224140712  Create bookcases
   up     20201225105949  Create notifications
   up     20201226102230  Add status to review
   up     20201230042031  Add caption to books

全てのマイグレーションファイルが実行されていることがわかりました。

データベースの確認
次にmysqlに接続し、デーベースのテーブルを確認しました。

$ docker-compose run web rails db
> show tables;

そうすると、以下のように表示されました。
booksテーブルは存在している?

+----------------------------------+
| Tables_in_share_read_development |
+----------------------------------+
| ar_internal_metadata             |
| bookcases                        |
| books                            |
| comments                         |
| favorites                        |
| notifications                    |
| relationships                    |
| reviews                          |
| schema_migrations                |
| users                            |
+----------------------------------+

しかし、以下のコマンドでは、booksテーブルがないと言われてしまいます。

> select * from books;

他のテーブルのデータは取得できます。

> select * from bookcases;
+----+------+---------------+---------+----------------------------+----------------------------+
| id | read | book_id       | user_id | created_at                 | updated_at                 |
+----+------+---------------+---------+----------------------------+----------------------------+
|  1 |    0 | 9784296108008 |       1 | 2020-12-28 01:18:40.540283 | 2020-12-28 01:18:40.540283 |
|  3 |    0 | 9784478820094 |       3 | 2020-12-28 12:39:00.697316 | 2020-12-28 12:39:00.697316 |
| 37 |    0 | 9784478820094 |       1 | 2020-12-29 08:16:25.944979 | 2020-12-29 08:16:25.944979 |
| 39 |    0 | 9784804614151 |       1 | 2020-12-29 08:26:56.615557 | 2020-12-29 08:26:56.615557 |
| 41 |    0 | 9784907095536 |       1 | 2020-12-29 08:27:01.301378 | 2020-12-29 08:27:01.301378 |
| 42 |    0 | 9784908925658 |       1 | 2020-12-29 08:39:11.191829 | 2020-12-29 08:39:11.191829 |
| 45 |    0 | 9784284204705 |       1 | 2020-12-30 04:10:08.090943 | 2020-12-30 04:10:08.090943 |
+----+------+---------------+---------+----------------------------+----------------------------+

公式ドキュメントを調べても、自身で試したことしか記載していませんでした。

エラー Table 'xxx' doesn't exist または Can't find file: 'xxx' (errno: 2) が発生する場合、xxx という名前のカレントデータベースにテーブルがないことを示しています。
SHOW TABLES を使用して、カレントデータベースにあるテーブルを確認できます。

【URL】
http://download.nust.na/pub6/mysql/doc/refman/4.1/ja/cannot-find-table.html

データベースの作り直し
あまり気が進みませんでしたが、データベースを作り直すことにしました。

$ docker-compose run web rails db:reset
$ docker-compose run web rails db:create
$ docker-compose run web rails db:migrate

これでエラーが解消することができました!
個人開発なので、データベースを削除するという方法で済みましたが、実際の現場ではこの方法は推奨されないと思います。
もし、今回のエラーの原因等がわかる方がいらっしゃいましたらコメントにて教えていただけると幸いです。

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

Refinementsで遊んでみた

はじめに

Refinementsで遊んでみた記事です。具体的な内容としてはトップレベルでRefinementsが使えないかなと試してみたとき備忘録です。

ブログからの転載です

Refinementsとは

Rubyでは、後から自由にクラスにメソッドを追加することができる。

class Integer
  def hoge
    puts :hoge
  end
end

42.hoge
# => :hoge

これはこれで便利なんだけど、グローバルに変更されるし、継承とかでも意図しない動きをする可能性があるのであまりいいとは言えない。

そこでRefinementsの出番。

module Refine
   refine Integer do
      def hoge
         p :hoge
      end
   end
end

# 42.hoge
# => Error!

using Refine

42.hoge
# => :hoge

Refinementsではusingした以降で再定義したものを使える。
またusingした箇所のファイルスコープに限定されるので影響範囲が小さくて済む。

やりたかったこと

以下のようにトップレベルでrefineを使って特定のクラスにメソッドを生やしたかった(毎回、usingとか書くのが面倒だったので……)

refine Integer do 
   def hoge
      puts :hoge 
   end
end

42.hoge

ただ、このコードはrefineというメソッドが定義されていないのでエラーになる。

やってみた

で、出来そうか試してみたところ以下のコードで動作した。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

refine Integer do 
   public
   def hoge
      p :hoge 
   end
end

42.hoge
# => :hoge

やっていること

Refinementsをよく使われる人は以下のようなコードで無名モジュールを使っているんじゃないかと思う。

using Module.new {
    refine Integer do
        def hoge
            p :hoge
        end
    end
}

42.hoge
# => :hoge

このコードを元にKernelモジュールにrefineメソッドを作成。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

refineメソッドは第一引数に再定義したいクラスなどを受け取り、メソッドの再定義などはブロック引数で渡します。

これにより以下のようにhogeメソッドを追加できます。

refine Integer do 
   public
   def hoge
      puts :hoge 
   end
end

42.hoge
# => :hoge

ただし……

ただ、このコードはusingしていないのに再定義した内容が使用できているんですよね……。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

# ここで usingを使っていないが作成したhogeメソッドが使えている……
refine Integer do 
   public
   def hoge
      p :hoge 
   end
end

42.hoge
# => :hoge

これはRefinementsの仕様通りの動きなのかがはっきりとしていないのでなぜ動いているのやら……。

それと今回のコードではpublicを追加しないとメソッドが呼び出されなかった。

module Kernel
    module_function
    def refine klass, &block
        Module.new do
           refine klass do
              yield
           end
        end
    end
end

refine Integer do 
   def hoge
      p :hoge 
   end
end

42.hoge
# => private method `hoge' called for 42:Integer (NoMethodError)

エラーからprivateメソッドとして定義されているらしいことがわかったのでpublicを付けてみたら動いたという……。
この辺も仕様通りの動きなんだろうか……?

おわりに

とりあえず、やりたいことはできた。
これが仕様通りの動きなのかは判断がつかないので、Refinementsの実装とか読んでみようかと思う。

参考

Ruby の refinements の使い途

【Ruby Advent Calendar 2018】あなたのしらない Refinements の世界【3日目】

Ruby で動的に refine を定義する

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

ActiveRecordのdefault_timezoneについて

想定バージョン

Ruby 2.5系
Ruby on Rails 5.2系

説明

ActiveRecordが発行するタイムスタンプのtimezoneはRailsの場合、config/application.rbで定義します。

# config/application.rb 

Rails.application.configure do
  config.active_record.default_timezone = :local
  # (ActiveRecord::Base.default_timezone = :localと同義)
  中略
end

この:localは何を指しているのかというとrubyプロセスタイムゾーンを指していて
rubyプロセスタイムゾーンはOSに定義されている環境変数のTZを見ています。
つまりこの環境変数を設定しないとActiveRecordはdefaultのUTCでタイムスタンプを発行してしまうことになります。

例えば以下のように定義すると、 created_atやupdated_atが東京の時間になります。

TZ=Asia/Tokyo

config.time_zoneとの違い

Railsで設定するタイムゾーンといえば、以下の方もあると思います。

Rails.application.configure do
  config.time_zone = 'Tokyo'
end

こちらはTime.zoneで取得するタイムゾーンを東京にできる設定となっています。

アプリケーション上でTime.zone.nowを叩くと東京の現在の時間が取得できるようになります。

参考

Railsタイムゾーンまとめ

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

部分テンプレートを使用する(保守性の高いコードを書く)

前書き

ある程度ポートフォリオが完成してきたところでコードの手直しを行なっていくことにしました。
実際の業務では保守性の高いコードが求められているからです。具体的には同じコードは一つにまとめておくことによって手直しするときに複数直さなくても済むように改善していきます。
書き方を忘れないように備忘録として残しておきます。

部分テンプレートの使用

ヘッダーは他のページでも繰り返し使用するので部分テンプレートとしてまとめていきます。
部分テンプレートとは、ビューファイルで繰り返し使用するコードを切り出し、再利用する仕組みのことをいいます。
部分テンプレートとして切り出すときに作成するファイルは命名規則として、アンダースコアを先頭に記述します。
今回の場合、_header.html.erbとなります。
app/views/shared/
header.html.erbディレクトリに作成していきます。

renderメソッド

renderメソッドは、部分テンプレートを呼び出す際に利用するメソッドのことです。
<%= render "ディレクトリの指定" %>と記述します。
例えばindex.html.erbやshow.html.erbでもヘッダーのコードを使いたい時にrenderメソッドで使い回すことができます。

index.html.erb
<%= render "shared/header" %>

<div id="home-index" class="contents row">
  <h2 class="page-title">Find your favorite!</h2>
  <p class="page-p">歌いたかったあの曲との出会い提供するMY KARAOKE。
  </p>
  <p class="page-p">このページではみんなが歌いたい曲の情報が満載です。
     みんなの曲を見てレパートリーを増やしましょう。
  </p>
省略
_header.html.erb
<header class='top-page-header wrapper'>
  <%= link_to 'MY KARAOKE', root_path, class: "title" %>

  <nav>
    <ul class="main-nav">
      <% if user_signed_in?%>
        <li><%= link_to current_user.nickname, new_user_session_path, class: "user-nickname" %></li>
        <li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete, class: "logout" %></li>
      <% else %>
        <li><%= link_to'ログイン', new_user_session_path, class: "login" %></li>
        <li><%= link_to '新規登録', new_user_registration_path, class: "sign-up" %></li>
        <li><%= link_to 'ゲストログイン', users_guest_sign_in_path, method: :post %></li>
      <% end %>
    </ul>
  </nav>
</header>

一行目の<%= render "shared/header" %>よって_header.html.erbのコードを使用することができます。
部分テンプレートを作成したことによってindex以外のページでも同じように使い回すことができます。

show.html.erb
<%= render "shared/header" %>

<div class="main">
  <div class="song-show">
    <table class="detail-table">
      <tbody>
        <tr>
          <th class="show-detail-value">投稿者</th>
          <td class="show-detail-song">
            <%= link_to user_path(@song.user_id) do %>
              <%= @song.user.nickname %>
            <% end %>
          </td> 
        </tr>
省略

今回はわかりやすいようにヘッダーの例を出しましたがもちろんそれ以外も部分テンプレートが使える場面があります。
例えばnewとeditは新規投稿する際にフォームなどはほぼ同じ記述になるので(少なくとも僕の場合は)使えたりします保守性の観点からまとめてあげるほうが後々楽になるのでやっておくほうがいいと思います。

後書き

とりあえず簡単な部分テンプレートいついてまとめました。 localsオプションを使った部分テンプレートの記事を次回書いてみようかと思っています。

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

【Rails奮闘記/Railsチュートリアル編】Railsチュートリアル6章で学んだこと

はじめに

今回はRailsチュートリアル6章を終えて学んだことをまとめたいと思う。
5章でUserコントローラーを作成したので、6章ではUserモデルを作成していくことになる。
早速確認してみます。

Userモデル作成

userモデルのデータ構造の基本。

users
id integer
name string
email string
created_at datetime
updated_at datetime

まずはUserモデルを以下のコマンドで作成。

$ rails generate model User name:string email:string

コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習を頭に入れておく必要がある。
コントローラはUsersでモデルはUserになる。
コマンドによって生成されたマイグレーションファイルについてみていく。

db/migrate/[timestamp]_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

マイグレーションは「データベースに与える変更を定義したchangeメソッドの集まり」を指している。
モデル名は単数形だが、テーブルは複数形になる。
マイグレーションファイルの最後のt.timestampsはマジックカラムを作成するものである。
ユーザーモデルに当てはめると、あるユーザーが作成または更新されたときにその時刻を自動的に記録するものになる。

マイグレーションファイルに問題がなければ、以下のコマンドでデータベースを作成する。

$ rails db:migrate

上記のコマンドを実行すると、データベースにUserテーブルが作成され、schema.rbが更新される。
以上がUserモデルを作成するまでの一連の流れになる。

Userモデルにバリデーションを追加

Railsチュートリアル6章では、presence・length・format・uniqueness・confirmationを実装していく。

presence

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true
end

length

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end

format

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }
end

uniqueness

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
end

emailにインデックスを追加する必要があるので、以下のコマンドを実行。

$ rails generate migration add_index_to_users_email

usersテーブルのemailカラムにインデックスを追加するために、add_indexというRailsのメソッドを使用している。
unique: trueを指定することで一意性を強制することができる。

db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[6.0]
  def change
    add_index :users, :email, unique: true
  end
end

マイグレーションファイルを修正したら、マイグレート。
これが終わったら、confirmationに。

confirmation

users
id integer
name string
email string
created_at datetime
updated_at datetime
password_digest string

セキュアなパスワードは、has_secure_passwordというRailsのメソッドを呼び出すことでほとんど完結できる。

usersテーブルにpassword_digestというカラムを追加する必要がある。
カラムを追加する際、以下のコマンドを叩く必要がある。

$ rails generate migration add_password_digest_to_users password_digest:string
db/migrate/[timestamp]_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :password_digest, :string
  end
end

add_columnメソッドを使ってusersテーブルにpassword_digestカラムを追加。
これを適用するために、データベースでマイグレーションを実行。

マイグレーションが終わったら、userモデルにパスワードを以下のように記述。
今回はパスワードにpresenceとlengthを入れている。

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
  validates :password, presence: true, length: { minimum: 6 }
end

これに合わせてテストを書いて、Railsチュートリアル6章は終わりである。

まとめ

6章から本格的にアプリ開発をしてる感があってようやく本番だなというのが正直なところ。
粘り強くやりぬきたいと思っている。

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

VScodeのスニペット機能を活用しよう ~ もう<%=%>はいちいち打ちたくない ~

概要

railsの開発をしていると、結構書くことになる「<%= %>」や「<% %>」。
いちいち打つのは結構めんどくさい。
なので、VScodeのスニペット機能を使って簡単にこれらのめんどくさいコードを打つ時間の短縮をしてみよう!
もちろん、他の言語でもOK!

導入の仕方

VScodeの左下の歯車マークをクリック

スクリーンショット 2021-01-16 13.39.34.png

Command paletteを開く

スクリーンショット 2021-01-16 13.36.46.png

snippetsと打って、省略したいコードの言語の拡張子を選択

スクリーンショット 2021-01-16 13.37.32.png

スクリーンショット 2021-01-16 13.38.04.png

スニペットを記述しよう

スクリーンショット 2021-01-16 13.38.35.png

自由にスニペットは作れるのですが、
と書き方は、以下のように
はじめの””の中には、当該スニペットの端的な内容
prefixの後には、スニペットを出力するためのトリガー
bodyの後には、実際出力したいコードの内容
descriptionの後には、スニペットの説明
を記述します!

ruby
{
    // Place your snippets for erb here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    "write <%=%> more easier": {
        "prefix": "pa",
        "body": [
            "<%= $0 %>"
        ],
        "description": "Log output to console"
    }
}

また、$0をかけば、カーソルの位置を指定できます。
上の例のようにかけば、<%= %>における=と%の間にカーソルが合います。
同様に、$1や$2をかけば、改行を挿入できます。

また、以下のように記述すれば複数のスニペットを作成できます。

ruby
{
    // Place your snippets for erb here. Each snippet is defined under a snippet name and has a prefix, body and 
    // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
    // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
    // same ids are connected.
    // Example:
    "write <%=%> more easier": {
        "prefix": "pa",
        "body": [
            "<%= $0 %>"
        ],
        "description": "Log output to console"
    },
    "write <%%>": {
        "prefix": "ni",
        "body": [
            "<%%>",
        ],
        "description": "Log output to console"
    }
}

終わり

Author: Kazuhito Nakayama
Twitter
Qiita

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

比較演算子と条件分岐

比較演算子とは

値どうしをを比較するときに使います。書き方は数学で学んだのと似てるようで微妙に違います。

比較演算子 条件式 意味
> A > B AはBより大きいか
>= A >= B AはB以上か
< A < B AはBより小さいか
<= A >= B AはBより大きいか
>= A <= B AはB以下か
== A == B AとBは等しいか

真偽値

真偽値とは式が正しいか違うかとことを表すもので
値が真であれば true
値が偽であれば false
で表される。

式の真偽の確認や、真偽値に対しての演算を行うことができる演算子を論理演算子と呼びます。

論理演算子 OR  ( | | )

真偽値と共に使われた場合、 演算対象のどちらかが true ならば、true を返し、両方とも false の場合は、false を返します。
A || B (AまたはBみたいなイメージ)

論理演算子 AND ( && )

真偽値と共に使われた場合、 演算対象の両方が true ならば、true を返し、そうでなければ、false を返します。
A && B (AかつBみたいなイメージ)

論理演算子 NOT ( ! )

!(エクスクラメーションマーク)は not演算子 と呼ばれ、否定の意味で使われます。
!と=を合わせた!=は、値同士が等しくない場合にtrueを返します。

A!=B(AとBは等しくない)

if文

もし〇〇なら□□するのように条件によって処理を分岐させる物です

if 条件式    #この条件式がtrueの場合に処理が実行される
  処理
end

elseを使うと条件式がfalseの時に実行する処理をかける

if 条件式
  # 条件式が真(true)のときに実行する処理
else
  # 条件式が偽(false)のときに実行する処理
end

elsifを使うとより複雑な条件処理を作れる

if 条件式1
  # 条件式1が真(true)のときに実行する処理
elsif 条件式2
  # 条件式1が偽(false)のとき、かつ
  # 条件式2が真(true)のときに実行する処理
else
  # 条件式1と条件式2がどちらとも偽(false)のときに実行する処理
end

以上です。

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

【新人注目】データベースの主キーと外部キーのお話

主キーとは

データベースを扱う際に、必ずひとつの情報を特定できるキーとなるものが必要になります。
例えば、大学でいう学籍番号のようなものです。
この番号があれば、S12H1234という学籍番号は田中太郎くんだというように、特定をすることができます。

データベースの表には必ずひとつひとつの行(レコード)を識別することができるように、キーとなる情報が必ず含まれるようになっています。これが主キーです。

主キーを構成する列に必要な条件

重複していないこと
空でないこと

このふたつが主キーに必要な条件。

外部キーとは

アプリケーションを開発する中で、使用するのがリレーショナルデータベースというものです。これはひとつひとつの表を関係付けできることが特徴になります。表と表とを関係付けるために、外部キーというものを使います。

学生表

学籍番号 名前 学部ID
S12463545 田中太郎 1
S12346456 鈴木一郎 2
S14345678 山田二郎 3

学部表

学部ID 学部名
1 法学部
2 経済学部
3 体育学部

上の表からすると、学生表では学部表の主キーを参照し、外部キーとして学部IDの列を設けています。
これにより、田中太郎くんの学部はどこ?という問いに対して、法学部というような返答ができるようになります。

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

Railsアプリにransackを導入したけどあたふたした話。

ポートフォリオ用のアプリにransackを使って検索機能を実装しようとした際にちょっと詰まったので
備忘録を残しておきます。

やりたいこと

検索文字列を含んだ投稿を検索する機能を実装したい。

ひとまずransackをインストール

Gemfile
gem 'ransack'
$ bundle install

コントローラーについて

posts_controller.rb
class PostsController < ApplicationController
  before_action :set_q, only: [:index, :search]

  def search
    @results = @q.result
  end

  private
    def set_q
      @q = Post.ransack(params[:q])
    end
end

ルーティングについて

routes
resources :posts do
  collection do
    get :search
  end
end

ビューについて

posts/index.html.erb
<%= search_form_for @q, url: search_posts_path do |f|%>
  <%= f.text_field :body_cont, placeholder: "検索"%>
  <%= f.submit "検索"%>
<% end%>
posts/search.html.erb
お好みにカスタマイズ

結論

色々な記事などに<%= f.search_field :body_cont %>
と記されていたのですが、これだとうまく行かなかったです。
ヤケクソで<%= f.text_field :body_cont %>にすると
すんなり行きましたー!

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

【Ruby初心者向け】puts print pの違いとは?実は明確な使い分けの方法があるんです!

Rubyを学習していたら、誰しもがputsメソッドやprintメソッド、pメソッドをよく見かけると思いますし、使うことも多々あるでしょう。

しかし、それぞれの違いをしっかりと理解していない人も少なくないのではないでしょうか。

というのも、私自身がその一人でした。

ある参考書ではputsを使って文字列などを出力しているけど、またある学習コンテンツではpを使って出力を行っている。そう思ったらprintメソッドというものもあるというのを知りました。

当時、Rubyの学習を始めたばかりで、それぞれの違いがわからない私は、どれを使ったら良いのかわからず混乱してしまいました。

当記事では、そのような方に向けてputs print pの3つのメソッドについて解説しています!

といってもそこまで難しいことでもないので、簡単に習得してしまいましょう!

puts、print、pメソッドの明確な違いとは?

簡単にいうとputs、print、pメソッドの違いとは、対象者の違いにあります。

putsとprintは一般ユーザー、pは開発者が対象者です。これがどういうことかというと、出力したい値が誰に向けたものかということです。

例えば、自身で開発したアプリケーションがあるとします。そのアプリケーションが実行される過程で、ユーザーに向けた何かを表示したい場合、putsやprintを使用します。

しかし、ここで注意が必要なのは、printは出力したいことの内容に改行がされないということです。

開発者側からしても、ユーザー側からしても、何か長い文章などが出力される時などに改行が行われないのはとても不便だと言えます。

ですので、一般的には一般ユーザに向けた出力をする場合はputsを使用します。

一方で、pは開発者に向けたメソッドです。

pのputsやprintとの大きな違いは、引数で渡されたオブジェクトがそのまま戻り値となるということです。

例えば、このようになります。

# 以下の内容を実行します。また、わかりやすくするためにirbなどで実行するための記述は省きます。
p "ダブルクオートも含まれた状態で出力されます" 

"ダブルクオートも含まれた状態で出力されます" # 出力内容
=>"ダブルクオートも含まれた状態で出力されます"

# putsで出力した場合
puts "ダブルクオートも含まれた状態で出力されます"

ダブルクオートも含まれた状態で出力されます # 出力内容
=> nil

どうでしょうか?意外と3つのメソッドには大きな違いがあることが分かったのではないでしょうか。

まとめ

puts、print、pメソッドはそれぞれ同じ引数を扱っていても、違う出力結果を表示することがわかりました。

初心者目線でいうと、それぞれの対象ユーザを気にして使っていくことをお勧めします。

簡単に、一般ユーザに向けた出力内容を求める場合はputsメソッド、開発者向けの出力内容を求める場合ならpメソッドを使えば良いでしょう。

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

【3行で説明】プログラミングスクールの技術記事を検索結果に表示させない方法

※単純に初歩的すぎる記事を見る必要がなくなった時に使う方法です。他意はありません。
1.Chromeでこの拡張機能(uBlacklist)を入れる。
2.表示してほしくないサイトに行く、または調べる
3.拡張機能のボタン(ブラウザ右上のパズルのようなマーク)から選択して、ブロックを押す。

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

PHPによる型の比較について

概要

以下の記載によるとPHPは比較対象の型は異なっていてもTRUEになってしまうらしい。
https://www.php.net/manual/ja/migration80.incompatible.php
0 == "0" はTRUEになるから、型の一致まで検証するなら 0 === "0" にする必要があるようです。

実践(PHP)

PHPで簡単な処理を書いてみました。
本当に0 == "0"はtrueになるみたいです。

<?php
if (0 == "0") {
  echo "true";
} else
  echo "false";
?>
実行結果
% php test.php 
true

次は以下のコードで実行してみます。
変更点は「if (0 == "0")」が「if (0 === "0")」に変わっただけです。

<?php
// 比較対象の型も検証する
if (0 === "0") {
  echo "true";
} else
  echo "false";
?>
実行結果
% php test.php 
false

型の検証まで行うとfalseになることを確認できました。

実践(Ruby)

Rubyだと「0 == "0"」は数値と文字列は比較不能なのでfalseになりました。
これは想定通りです。

if 0 == "0" then
  puts "true"
else
  puts "false"
end
実行結果
% ruby test.rb 
false

実践(Python)

Pythonでも「0 == "0"」は型が違うのでfalseになりました。
これも想定通りです。

if 0 == "0":
    print("true")
else:
    print("false")
% python test.py 
false

まとめ

PHPはRubyやPythonと設計思想が違うようです。
PHPは比較対象の内容が合ってればいい、ということで数値の0も文字列の0もtrueになっちゃうみたいですね。

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

[Rails]"kaminari"の使い方について

はじめに

"kaminari"の実装をやったので忘れないように記していきます。:zap:

「kaminari」

ページネーションを実装するためのrubyのgemの一つです。

何かの一覧ページを表示した時に、たくさんある場合見やすいようにページを数字で分割してくれ、最初や最後のページに飛べるボタンが実装できたりします。

実装

インストール

まずはgemをインストールします。
日本語表記にするので一緒に「i18n」もインストールします。

「i18n」は以前学習していましたのでいちお貼っておきます。

[Rails]日本語表記にする方法

Gemfile
gem 'kaminari'
gem 'rails-i18n'

インストール!


bundle install

コントローラ

controller.rb
def index
    @advises = Advise.page(params[:page])
  end

ビューファイル

view.html.erb
 <div class="advise-lists">
   <% @advises.each do |advise| %>
     <ul class="advise-text">
       <li><%= link_to advise.title, advise_path(advise), class: "advise2_link" %></li>
     </ul>
   <% end %>     <!--↑ 一覧表示の部分-->            
 </div>         <!--↓ ページネーション実装部分-->      

 <%= paginate @advises %>


「kaminari」の設定ファイルを作成


rails g kaminari:config

「kaminari_config」ファイルが作成されます。
お好みで変更していきます。

こちらでは「default_per_page」を1ページあたり3行とわざと小さく設定しています。

config/initializers/kaminari_config.rb
# frozen_string_literal: true

Kaminari.configure do |config|
 config.default_per_page = 3
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
  # config.max_pages = nil
  # config.params_on_first_page = false
end

たくさん項目がありますが
こちらでは
フリーランスLIFE!:【Rails】でページネーションをgemで実装!

TECH SCORE BLOG:Railsライブラリ紹介: ページングを行う「kaminari」

以下のように説明されています。

default_per_page
デフォルトのページあたりの表示件数(デフォルトは 25)。

max_per_page
ページあたりの表示件数の最大(デフォルトは nil、つまり無制限)。

window
表示中のページの左右何ページ分のリンクを表示するかを指定します(デフォルトは 4)。上記画像はデフォルトの 4 で、11 ページを表示しているところです。11 の左右それぞれ 4 ページ分のリンクが生成されています。

outer_window
先頭ページ、及び最終ページから何ページ分のリンクを表示するかを指定します(デフォルトは 0)。left、right が指定された場合は、そちらの値が優先されます。

left
先頭ページから何ページ分のリンクを表示するかを指定します(デフォルトは 0)。上記画像は 3 を指定した場合です。

right
最終ページから何ページ分のリンクを表示するかを指定します(デフォルトは 0)。上記画像は 2 を指定した場合です。

page_method_name
モデルに追加されるページ番号を指定するスコープの名前(デフォルトは page)。

param_name
ページ番号を渡すために使用するリクエストパラメータの名前(デフォルトは page)。

引用: Railsライブラリ紹介: ページングを行う「kaminari」

ロケールの設定

ロケールファイルのロードパスを設定します。
デフォルトロケールを「日本語( ja )」にセットします。

config/application.rb
config.i18n.load_path +=
      Dir[Rails.root.join("config", "locales", "**", "*.{ry,yml}").to_s]
    config.i18n.default_locale = :ja

viewディレクトリを作り、その下にファイルを作ります。

locales/views/pagenate.ja.yml
ja:
  views:
    pagination:
      first: "先頭"
      last: "最後"
      previous: "前へ"
      next: "次へ"
      truncate: "..."

そうすると英語表記から日本語表記に代わって表示され、ページネーションが実装されます。

スクリーンショット 2021-01-15 23.30.18.png

さらにカスタマイズをしたい場合は


rails g kaminari:views default

を入力すると
viewにこれらが作成されます。

スクリーンショット 2021-01-15 23.35.38.png

これらのファイルを編集すれば、ページネートのビューを変更することができます。
(_gap.html.erbはページが省略される"..."の部分、
_page.html.erbはページ番号の部分、
_paginator.html.erbは全体の構成定義)

まとめ

装着するの自体はそんなに難しくはないですが、カスタマイズの仕方をもう少し触って勉強していきたいと思います。
装飾とカスタマイズの部分をまた近々やっていきたいと思います。
    
       :runner::dash:  :runner: :runner::runner::dash:

参考

kikawaka:【Rails】kaminariの使い方をマスターしよう!

設定項目を確認するには・・

RAILS GUIDES:Configuring Rails Applications

装飾の参考

ぽてちる:【Rails】kaminariを使ってページネーションを実装する方法

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