20191230のRubyに関する記事は16件です。

RubyとRailsのバージョンの変更方法

Rubyのバージョン変更方法

①利用中のRubyバージョン確認

$ ruby -v

②インストール可能なRubyバージョン確認

$ rbenv install --list

③目的のバージョンをインストール(1-2時間かかります)

$ rbenv install 2.6.3

④利用するバージョンを変更

$ rbenv global 2.6.3

⑤現在コンソール上で利用しているRubyバージョン確認

$ rbenv versions

⑥rehashしてあげる ※ファイルの場所を変更して使えるようにする

$ rbenv rehash

指定したRailsのバージョンでプロジェクトを立ち上げる方法

①現在インストールされていて、使用できるRailsの確認

$ gem list rails

②Railsの使用したいバージョンをインストール(使用したいバージョンが無かった場合)

$ gem install -v 5.1.7

もう一度、使用できるRailsの確認をすると追加されている

$ gem list rails

③バージョンを指定してプロジェクトを立ち上げる

$ rails _5.1.6_ new sample_app
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】スクレイピングを使ってブログの記事一覧に記事内の写真を添える

はじめに

今回はRubyでスクレイピングに挑戦していきます。

前回の記事でブログを作成しました。
【Rails】summernoteを使って画像も投稿できるブログを作る

ブログといえばはてなブログですが、
スクリーンショット 2019-12-30 20.59.20.png
知り合いのお店のブログです)

このようにその記事内で使われている写真が横に乗るようになっています。

これを実現してみたい。。

前回記事で使ったsummernoteですが、画像を投稿するとbase64でimgタグで直保存されます。

例えばこんな感じです

<p>ホゲホゲ</p>
<p><img src="data:image/jpeg;base64,(長いので省略)" data-filename="4.jpg" style="width: 100px;"><br></p>

ブログ内で写真が使われていればどこかにimgタグがあるはずなのでこれをブログの保存時に検知すれば良いのでは?
と僕は考えました。

ということで、もっと簡単な方法もありそうなものですが、丁度スクレイピングにも興味があったのでやってみます。

実装!

こちらの記事を参考にしました。
https://morizyun.github.io/blog/ruby-nokogiri-scraping-tutorial/index.html
https://blog.takuros.net/entry/2014/04/15/070434

僕はnokogiriがすでにインストールされていたのでされていない人は上記の記事を参考にインストールしてみてください。(Gemいじるだけなので簡単だと思います)

今回はDBに保存する前のデータを使用するので他のページから取ってくるという工程は不要になります。

スクリーンショット 2019-12-30 21.50.52.png

imageというカラムに挿入することにします。

スクレイピングしてimgタグを:imageに保存

blog_posts_controller.rb
  @charset="UTF-8"

  def create
    @blog_post = BlogPost.new(blog_post_params)
    @blog_post[:created_by] = current_user.id
    @blog_post[:updated_by] = current_user.id

    doc = Nokogiri::HTML.parse(@blog_post[:content], nil, @charset)
    images = doc.at('//img')
    if images.instance_of?(Array) then
      @blog_post[:image] = images[0]
    else
      @blog_post[:image] = images
    end

    respond_to do |format|
      if @blog_post.save
        format.html { redirect_to @blog_post, notice: 'Blog post was successfully created.' }
        format.json { render :show, status: :created, location: @blog_post }
      else
        format.html { render :new }
        format.json { render json: @blog_post.errors, status: :unprocessable_entity }
      end
    end
  end

doc = Nokogiri::HTML.parse(@blog_post[:content], nil, @charset)
blog_post[:content]の内容全てをNokogiri::HTML::Documentに変換しています。

その後のimages = doc.at('//img')でimgタグの一番初めにヒットしたものを持ってきています。

これで

blog_post[content]
<p>ホゲホゲ</p>
<p><img src="data:image/jpeg;base64,(長いので省略A)" data-filename="4.jpg" style="width: 100px;"><br>
<img src="data:image/jpeg;base64,(長いので省略B)" data-filename="4.jpg" style="width: 100px;">
</p>

このような:contentの中身が

blog_post[image]
<p><img src="data:image/jpeg;base64,(長いので省略A)" data-filename="4.jpg" style="width: 100px;"><br></p>

このように:content内の初めのimgタグの内容が:imageに保存されます。

ブログの記事一覧の横に表示

blog_post/index.html.haml
    - @blog_posts.each do |blog_post|
      %table
        %thead
          %tr
            %th
            %th

        %tbody
          %tr
            %td{style: "max-width: 100px; height: auto;"}
              -if !blog_post.image.nil?
                = link_to blog_post.image.html_safe, blog_post
              -else
                =image_tag("hoge.jpg", :size => '100x100', style:"max-height: 100px")
            %td= link_to blog_post.title, blog_post
          %hr

style: "max-width: 100px; height: auto;"を指定しているのは、そのまま表示すると:image内の画像がでかすぎた時対策です。

また、blog_post.content内にimgタグが存在しなかった時、blog_post.imageがnilになってエラーが出てしまうため、仮の画像を入れるようにしています。

終わりに

nokogiri便利でした。
これと似たような流れでその記事のはじめの方をダイジェストでだすということもできそうです。

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

【Rails】NoMethodError (undefined method `current_sign_in_at' for #<User:XXXXXXXXXXXXXX>):

はじめに

Railsでgem devise_token_authを導入時に発生した内容です。

Eメール認証の確認をしようと、
http://localhost:3000/api/v1/auth/sign_in
にPOSTリクエストを出すと以下エラーが発生しました。

Started POST "/api/v1/auth/sign_in" for ... Processing by ... DeviseTokenAuth::SessionsController#create as JSON
...

Completed 500 Internal Server Error in 311ms (ActiveRecord: 17.4ms | Allocations: 7912)

NoMethodError (undefined method `current_sign_in_ip' for #<User:...>):

※rails serverより抜粋。

このエラーに対して、自分がうまくいった解決法を残します。

環境

OS: macOS Catalina 10.15.1
Ruby: 2.6.5
Rails: 6.0.2.1

結論:user.rbを修正

Before

app/models/user.rb
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  include DeviseTokenAuth::Concerns::User
end

After

app/models/user.rb
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end

:trackableを削除すると無事に通るようになりました。

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

どなたかの参考になれば幸いです:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

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

【Rails】summernoteを使って画像も投稿できるブログを作る

はじめに

summernoteを使って画像の投稿などにも対応したブログを作っていきたいと思います。

何か間違っている点があればコメントをください…!

レシピ

1. ER図を書く

データ構造をまとめます。ER図の作成はpgmodelerを僕は使用しています。
スクリーンショット 2019-12-28 19.01.28.png

こんな感じになりました。

2. generator使って丸っと全体像を作成

Rails初心者の僕はgeneratorに頼りきっています。

$ rails generate scaffold Blog_post title:string content:text created_by:string updated_by:string

3. migrationファイルの編集

not nullを指定したいのでmigrationファイルに直接書き込みます。

変更前

db/migrate/20191228075319_create_blog_posts.rb
class CreateBlogPosts < ActiveRecord::Migration[5.1]
  def change
    create_table :blog_posts do |t|
      t.string :title
      t.text :content
      t.string :created_by
      t.string :updated_by

      t.timestamps
    end
  end
end

変更後

db/migrate/20191228075319_create_blog_posts.rb
class CreateBlogPosts < ActiveRecord::Migration[5.1]
  def change
    create_table :blog_posts do |t|
      t.string :title, null: false
      t.text :content, null: false
      t.string :created_by, null: false
      t.string :updated_by, null: false

      t.timestamps null: false
    end
  end
end

4. migrationする

migrateファイルの編集も終わったのでmigrationをかけます。

$ bundle exec rake db:migrate

これでblog_postsテーブルがDB上に作成されます。

5. form.htmlを編集

created_by、updated_byがフォームで作成する感じになっているので内部での処理に置き換えます。

_form.html.haml
= form_for @blog_post do |f|
  - if @blog_post.errors.any?
    #error_explanation
      %h2= "#{pluralize(@blog_post.errors.count, "error")} prohibited this blog_post from being saved:"
      %ul
        - @blog_post.errors.full_messages.each do |message|
          %li= message

  .field
    = f.label :title
    = f.text_field :title
  .field
    = f.label :content
    = f.text_area :content
--  .field
--    = f.label :created_by
--    = f.text_field :created_by
--  .field
--    = f.label :updated_by
--    = f.text_field :updated_by
  .actions
    = f.submit 'Save'

(-部分を削除です)

6. created_by、updated_byを内部で入れる

  # POST /blog_posts
  # POST /blog_posts.json
  def create
    @blog_post = BlogPost.new(blog_post_params)
+    blog_post[:created_by] = current_user.id
+    blog_post[:updated_by] = current_user.id

    respond_to do |format|
      if @blog_post.save
        format.html { redirect_to @blog_post, notice: 'Blog post was successfully created.' }
        format.json { render :show, status: :created, location: @blog_post }
      else
        format.html { render :new }
        format.json { render json: @blog_post.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /blog_posts/1
  # PATCH/PUT /blog_posts/1.json
  def update
+    blog_post[:updated_by] = current_user.id
    respond_to do |format|
      if @blog_post.update(blog_post_params)
        format.html { redirect_to @blog_post, notice: 'Blog post was successfully updated.' }
        format.json { render :show, status: :ok, location: @blog_post }
      else
        format.html { render :edit }
        format.json { render json: @blog_post.errors, status: :unprocessable_entity }
      end
    end
  end

(+を追加です)

7. summernoteの導入

summernoteを使用します。
スクリーンショット 2019-12-28 19.35.03.png
このようなテキストエディタが導入できます。

これで画像の投稿まで対応できます。

https://blog.seiyamaeda.com/12552
https://qiita.com/Kohei_Kishimoto0214/items/a7c75d105d530d81a678

これらの記事を参考に導入します。(導入過程は省略)

_form.html.haml
= form_for @blog_post do |f|
  - if @blog_post.errors.any?
    #error_explanation
      %h2= "#{pluralize(@blog_post.errors.count, "error")} prohibited this blog_post from being saved:"
      %ul
        - @blog_post.errors.full_messages.each do |message|
          %li= message

  .field
    = f.label :title
    = f.text_field :title
  .field
    = f.label :content
    = f.text_area :content, 'data-provider': :summernote
  .actions
    = f.submit 'Save'

%div#summernote
:javascript
  $(function () {
    $('[data-provider="summernote"]').each(function(){
      $(this).summernote({
        lang: 'ja-JP',
        height: 250,
        fontNames: ['Helvetica', 'sans-serif', 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New'],
        fontNamesIgnoreCheck: ['Helvetica', 'sans-serif', 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New'],
      });
    })
  })

最終的にこのような形でformに実装します。

8. html_safeでhtml形式のcontentsがちゃんと表示されるようにする

summernoteによりDBに保存すると
<p>test</p>
のようにhtml形式で保存されます。

generatorで作成されたshow, indexのままだとそのまま

なども表示してしまうのでこれを修正します。

contentに対してhtml_safeというメソッドをかけることで修正できます。

show.html.haml
%p#notice= notice

%p
  %b Title:
  = @blog_post.title
%p
  %b Content:
  = @blog_post.content.html_safe

= link_to 'Edit', edit_blog_post_path(@blog_post)
\|
= link_to 'Back', blog_posts_path
index.html.haml
%h1 Listing blog_posts

%table
  %thead
    %tr
      %th Title
      %th Content
      %th
      %th
      %th

  %tbody
    - @blog_posts.each do |blog_post|
      %tr
        %td= blog_post.title
        %td= blog_post.content.html_safe
        %td= link_to 'Show', blog_post
        %td= link_to 'Edit', edit_blog_post_path(blog_post)
        %td= link_to 'Destroy', blog_post, method: :delete, data: { confirm: 'Are you sure?' }

%br

= link_to 'New Blog post', new_blog_post_path

ついでにcreated_byなどの余計な情報は表示しないように変更しました。

終わりに

これで完成です。

summernoteを取り上げている記事少ないですけどめちゃ便利ですねこれ。

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

Ubuntu 19.10のRedmineでのいくつかのバグフィックスをした

Ubuntu 19.10のRedmineでのいくつかのバグフィックスをした

これまでもアップしたのも含め、Ubuntu 19.10のRedmineでは以下の不具合がありました。
致命的だったモーダルダイアログの件以外はちゃんとバグレポート上げれてないけど...。

  • jQuery UIのバージョン違いのため、ダイアログが表示されない。
  • jQuery UIのバージョン違いのため、Closeボタンの上に「Close」というテキストが表示されてしまう。
  • ガントチャートでカミナリ線・関係線が表示されない。
  • チケットのインポートができない。

他にも以下の便利機能を足してみました。

jQueryのバージョン違いについて

いくつかのメンバ関数がプロパティに変更されているようです。
モーダルダイアログやガントチャートのイナズマ線・関係線が表示されないのはこのためでした。
今回修正したのは以下のものです。

旧 (メンバ関数) 新 (プロパティ)
zIndex() zIndex
size() length

元々RubyもJavaScriptもちゃんと修めないまま修正にかかったので、もっとも手強かったのが「モーダルダイアログのクローズボタンに "Close" というテキストが表示されてしまう」というものでした。
Ubuntu (Debian) のディストリビューションではなぜか、jQeuryはシステムのlibjs-jqueryパッケージを使っているのに、jQuery UIはlibjs-jquery-uiパッケージ以外にもCSSでは一部ローカルコピーを使っていたりと、謎です。でも、jQuery UIを完全にUbuntuディストリビューションのものにするのもしんどかったので、とりあえずで、redmineパッケージにコピーしてあるJQuery UI関連のファイルを1.11.0からUbuntu 19.10のパッケージバージョンである1.12.1に置き換えました。
これで、Closeの文字も表示されなくなりました。

変更したファイル

今回は、テキストファイルだけではなくバイナリファイルなんかも差し替えているので、単純な差分に収まりきらなかったためGitHubに上げておきました。

必要な方はあくまでも自己責任で、develブランチをcloneした上で、debパッケージを作ってインストールしてください。パッケージをビルドするには以下のコマンド (いくつかのパッケージのインストールが必要ですが)。

パッケージの作成に必要な前準備はここ:debパッケージ作成方法をステップバイステップでまとめました

パッケージの作成は以下のコマンド。

debuild -b

テキトーなインストールスクリプト。

install.sh
#!/bin/bash
version="4.0.4-1.ubuntu1.katsuya1"
dpkg -i redmine_"$version"_all.deb \
     redmine-mysql_"$version"_all.deb \
     redmine-sqlite_"$version"_all.deb 

MySQLでなくPostgreSQLの方は適当に書き換えてください。

これで、当面Redmineに感じていた課題感はクリアかな。

でもね

ライブラリを一本化したいというDebianの理想は十分に理解するものの、他にもバージョン違いに由来する不具合が残っていそうなので、素直にRedmineのディストリビューションで期待するjQueryをRedmineローカルで用意する方が良いような気がする...。

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

Railsチュートリアルメモ - 第1章

目次はこちら

Railsチュートリアル(ローカル版)の第1章をやってみて気づいたことのメモを記載していきます

1.1 はじめに

割愛

1.2 さっそく動かす

ローカル(mac)で普通にチュートリアル通り実行していくと、最初の

printf "install: --no-document \nupdate:  --no-document\n" >> ~/.gemrc
gem install rails -v 5.1.6

Fetching: concurrent-ruby-1.1.5.gem (100%)
ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.

となる。
ググると、system標準のrubyを使おうとしているため、権限エラーになっているようなので、rbenvをインストールして、別のrubyを使用(バージョンは2.5系で最新だった2.5.7を指定)

brew install rbenv

echo 'export PATH="~/.rbenv/shims:/usr/local/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

rbenv install 2.5.7
rbenv global 2.5.7
rbenv versions
gem install rails -v 5.1.6

今度は無事railsをインストールできた

1.3 最初のアプリケーション

1.3.1 Bundler

Gemfileとbundlerの使い方についての章

gemはrubyにおけるライブラリのこと。bundlerを使うとgemのバージョンを管理できる。
(pythonでいうところの、requirements.txtがGemfile、pipがbundler)
なお、bundler自身もgemとのこと。
Gemfile.lockというファイルがセットで存在し、.lockの方は実際にインストールしたgemが反映される。

使い方は、
1. Gemfileに使用するgemを記載
2. bundle installでGemfileに記載されたgemをインストールしてくれる

なお、GemfileとGemfile.lockに同じライブラリが記載されているがバージョンに差異がある場合(Gemfileのバージョンを変えたときなどに発生)には、bundle updateが必要。
bundle installはあくまで、未インストールのgemのインストールしかやってくれず、gemのバージョン更新はしないらしい。

1.3.2 rails server

割愛

1.3.3 Model-View-Controller (MVC)

割愛(大事なところなので、MVCについてよく知らない人はちゃんと読んでください)

1.3.4 Hello, world!

コントローラーに変更を加え、ルーティング(URLと呼び出される処理のマッピング)を変える。
ルーティングはconfig/routes.rbに定義する。

config/routes.rb
Rails.application.routes.draw do
  root 'application#hello'
end

↑に対して、application_controller.rbのhelloメソッドが呼び出される

1.4 Gitによるバージョン管理

割愛

1.5 デプロイする

herokuにデプロイしていく。
herokuのCLIが必要になるので、homebrewでインストールする(公式にcatalinaだとwarningが出ると書いてあったが、mojaveだと特に何も起きなかったので気にせず進める)

brew tap heroku/brew && brew install heroku

コマンドラインからログインし、sshキーをherokuに登録し、heroku createで新規アプリケーションを作成する

herokuにgit pushしようとすると以下のエラーが発生。

git push heroku master
fatal: 'heroku' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

調べてみると、ローカルのフォルダ構成が以下になってしまっていた。どうもheroku createすると、カレントディレクトリでgit initが実行され、.git/configのremoteにherokuが追加されるらしい。

tree
.
├──.git/config
└── environment
    └── hello_app
        └──.git/config

hello_app配下の.gitディレクトリを削除し、プロジェクトルートで再度heroku create実行からやり直すと解消。

無事、herokuにデプロイできた。

1.6 最後に

割愛

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

Railsチュートリアルメモ 目次

はじめに

  • ここ1年半くらいpythonをメインに触っていたが、転職サイトを眺めていたらまだまだRailsの需要が高い(特にスタートアップ界隈)なことに気づき、ちょうど年末で暇なので久々にRails チュートリアルを復習してみようと思った
  • 以前学習したときはcloud9がawsに吸収される前だったのでcloud9を使うことに抵抗がなかったが、rails tutorialをやるためにわざわざawsにログインしてcloud9使うのは手間だなと思ったので、ローカルでやってみることにした(aws経由じゃなくても使える?みたいだが、まぁ普通にローカルの方がサクサク開発できるし)
  • エディタはVSCodeを使用しています
  • 章ごとに気づいたことをまとめていきます(途中で力尽きたらごめんなさい)

目次

第1章メモ
第2章メモ※後日記載予定
第3章メモ※後日記載予定
第4章メモ※後日記載予定
第5章メモ※後日記載予定
第6章メモ※後日記載予定
第7章メモ※後日記載予定
第8章メモ※後日記載予定
第9章メモ※後日記載予定
第10章メモ※後日記載予定
第11章メモ※後日記載予定
第12章メモ※後日記載予定
第13章メモ※後日記載予定
第14章メモ※後日記載予定

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

rails: Arrayでもwill_paginateを使えるようする方法

前提

  1. gem pagenateは事前に使える状態にしておく。
  2. bundle installから使える状態までにする方法に関してはREADMEを参考にしてください。
    FYI: will_paginate

結論から言うと、デフォルトではwill_paginateはarray型の時は使えない。
そのため、config/initializersの配下にちょっとした記述をする必要がある。
ファイル名はなんでもいいのでとりあえずファイルを作る。(ex: will_paginate_array.rb)
ファイル作成後は以下の一文を追記する。

# will_paginate_array.rb
require 'will_paginate/array'

記述後、一旦railsを再起動する。
で使えるようになるはず。

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

Railsチュートリアル 3章までのエラー(自分用メモ)

Rails チュートリアル 3章までのエラーと対処法

ProgateでRuby, Rails, Git, HTML&CSS 終了後にスタートしました。
事前知識は大学の授業でC言語を少し扱ったくらい。

~1章

環境構築でのエラー

ProgateのRuby on Railsの環境構築の記事を見ながら進めていったとところ、rails new でフォルダが生成されず。

調べたところRails5.0以上ではNode.jsが必要なようで、インストールし解決しました。

Herokuへのデブロイ

その後1.5までは順調に進められましたがHerokuへのデブロイでエラー。

$ git push heroku master 

$ heroku open
すると、Application errorの画面が。

heroku logsでエラーの確認をしてもよく分からず、2日ほど試行錯誤。
その後あきらめてteratailで質問を投稿したところ、Rails6でのデブロイの方法がRails5までと異なるという回答を頂きました。

そこで初めて自分がRails6を使っていることに気がつき、バージョンを指定してRails newからやり直し。Rails6でのデブロイは複雑だったのであきらめました。

Rails6でのデブロイ

しかしそれでもデブロイできず。

エラー文をよく見るとApp crashとありました。それを元に色々調べると、postgresqlを使用することを、

config/database.yml

で明記する必要があるようでした。

修正前

database.yml
production:
  <<: *default

  database: db/production.sqlite3

修正後

database.yml
production:
  <<: *default
  adapter: postgresql
  database: db/production.postgresql

Gemfileを書き換えるだけではだめだったようです。ここのエラーが今までで一番時間がかかり、3日くらい取られてしまいました。

2,3章

2章、3章ではそこまで大きくつまづくことはありませんでしたが、Gemfile変更後にbundle installや、Herokuへデブロイする前にcommitするのを忘れるミスがかなりありました。

まとめ

Progateは1周しかしておらず、かなり答えを見てしまうくらいの理解度だったのでまだRails チュートリアルは早いかと思いましたが、2週するのはめんどうだったので始めてみました。

期間は3章までで1週間くらい。今のところ少し時間がかかっていますが、1月中に終了を目標にがんばっていきます。

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

rails db:migrate でエラーが起きる時の対処法

今回は、マイグレーションをした時の対処法についてみていきます。

$ rails db:migrate
-- create_table(:movies)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::DuplicateTable: ERROR:  relation "movies" already exists

このようなエラーはすでに、同じ「movies」というテーブルが存在していますよ。っていう意味なので、moviesテーブルを削除してあげてれば問題ないです。

すべきこと

1.ターミナルで以下のように入力してあげる。

$ rails db

2.#が出てくるので、その横に\dと入力してあげる

# \d

3.テーブルがたくさん出てくるので該当のテーブルを削除してあげる。(今回は、moviesテーブルを削除する)

# drop table movies;

4.再び,マイグレコマンドを入力する

$ rails db:migrate 

これで、マイグレーションがうまくいきます。

もし、間違えているところがあればコメントしていただけると幸いです。
また、いいねを頂くと励みになりますのでよろしくお願い致します。

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

プログラミング勉強

今日は基礎の二週目をスタートし、テストの復習を行う?
頑張るぞ?

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

Rubyのキーワード引数はシンボルっぽく定義するけど、シンボルそのものではない、という話

はじめに

拙著「プロを目指す人のためのRuby入門(通称チェリー本)」の読者さんの中で、ときどきキーワード引数について困惑している人を見かけるので、この記事でキーワード引数に関する説明を補足しておきます。

Ruby初心者さんの疑問:キーワード引数はシンボルじゃないの?

Ruby初心者さんの中には、キーワード引数をシンボルとして参照できないことに疑問を持つ方がおられるようです。

たとえばこんな感じです。(「プロを目指す人のためのRuby入門」5.4.3項のコード例を一部改変)

def buy_burger(menu, drink: true, potato: true)
  # なんで :drink じゃないの?
  if drink
    # ...
  end
  # なんで :potato じゃないの?
  if potato
    # ...
  end
end

なるほど、そう言われてみると、メソッド定義のキーワード引数はたしかにキーがシンボルのハッシュっぽい書き方になってますね・・・。

# drink: true, potato: true の部分だけ見ると、
# たしかにキーがシンボルのハッシュっぽい
def buy_burger(menu, drink: true, potato: true)

# 実際、こう書けばキーがシンボルのハッシュになる
hash = { drink: true, potato: true }
hash[:drink]  #=> true
hash[:potato] #=> true

でも、これは記法が似ているだけであって、シンボルではないんです。
あくまでメソッドの引数(仮引数)なんです。

ですので、上のメソッドのmenuと同じように、メソッド内ではdrinkpotatoのように参照します。

もし、:drink:potatoのように書いてしまうと、これはシンボルオブジェクト(シンボルリテラル)を書いたことになります。

def buy_burger(menu, drink: true, potato: true)
  # キーワード引数もメソッドの引数なので、menuと同様にdrinkと書く
  if drink
    # ...
  end

  # :drinkと書いた場合はメソッドの引数とは無関係な、ただのシンボルオブジェクトになってしまう
  if :drink
    # ...
  end

  # ...
end

とりあえず、この点については「キーワード引数はシンボルではないんです。定義するときの書き方はたしかに似てるけど、そういうもんなんです」としか説明のしようがありません。

なぜキーワード引数をシンボルっぽく書くのか?その歴史的経緯

とはいえ、キーワード引数をシンボルっぽく書くのは、おそらく歴史的な経緯があると考えています。

プロを目指す人のためのRuby入門」の5.6.3項にも書いたように、キーワード引数が導入される前(つまりRuby 1.9以前)はハッシュを使って擬似的にキーワード引数を実現していました。

# ハッシュを引数として受け取り、疑似キーワード引数を実現する
# (キーワード引数が導入される前はこう書くしかなかった)
def buy_burger(menu, options = {})
  # 引数のハッシュから目的の値をローカル変数に格納する
  drink = options[:drink]
  potato = options[:potato]

  # 引数から取り出した値を使って処理を書く
  if drink
    # ...
  end

  # ...
end

# このメソッド呼び出しではキーワード引数ではなく、
# { drink: true, potato: true } というハッシュオブジェクトを
# 引数として渡している
buy_burger('cheese', drink: true, potato: true)

このようにRubyの世界では昔からハッシュとキーワード引数が近しい関係にあったため、キーワード引数の定義の仕方もハッシュの記法に似たものになったのだろう、と僕は考えています。

# 昔はみんなハッシュで書いてたんだから・・・
def buy_burger(menu, options = {})
  # ...
end

# キーワード引数もハッシュっぽい記法になっていると自然(?)
def buy_burger(menu, drink: true, potato: true)
  # キーワード引数にすると、このローカル変数が引数に置きかわるイメージ
  # drink = options[:drink]
  # potato = options[:potato]

  if drink
    # ...
  end

  # ...
end

# ちなみに、呼び出し方はどちらも変わりません
buy_burger('cheese', drink: true, potato: true)

ただ、この歴史的経緯を知っている人にとっては「うん、自然だね」と思えるものの、そうじゃない人にとっては「えっ、なんでシンボルじゃないの!?」とビックリする原因になるのかもしれませんね。

まとめ

というわけで、この記事では「Rubyのキーワード引数はシンボルっぽく定義するけど、シンボルそのものではない」という話を説明しました。

プロを目指す人のためのRuby入門」の改訂版を出す機会があれば、この内容も追加しておこうと思います。
それまではこの記事を参考にしてもらえると幸いです。

あわせて読みたい

Ruby 2.0でキーワード引数が導入されたときに書かれた記事です。
上で説明した「歴史的経緯」をより詳しく理解することができます。

一方で、この「歴史的経緯」が原因で、Ruby本体のややこしい不具合の原因になってしまったという問題もあります。
そのため、Ruby 2.7以降ではキーワード引数の扱いが変更されています。
詳しくはこちらの記事をご覧ください。

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

Rubyの配列の要素をn個の配列へ順繰りに振り分け

配列や範囲オブジェクトなどに対し以下のように動作する Enumerable#balance を実装したい。ロードバランサーのラウンドロビン方式みたいな感じ。

pp (:A..:Z).balance(7) # 均等にならない場合
#=> [[:A, :H, :O, :V],
#    [:B, :I, :P, :W],
#    [:C, :J, :Q, :X],
#    [:D, :K, :R, :Y],
#    [:E, :L, :S, :Z],
#    [:F, :M, :T],
#    [:G, :N, :U]]

pp (:Α..:Ω).balance(4) # 均等になる場合
#=> [[:Α, :Ε, :Ι, :Ν, :Ρ, :Φ],
#    [:Β, :Ζ, :Κ, :Ξ, :Σ, :Χ],
#    [:Γ, :Η, :Λ, :Ο, :Τ, :Ψ],
#    [:Δ, :Θ, :Μ, :Π, :Υ, :Ω]]

pp [].balance(3) # 空配列の場合
#=> [[], [], []]

作ったらこうなった。

module Enumerable
  def balance(n)
    m = nil
    each_slice(n).to_a.tap do |*, la|
      return Array.new(n) { [] } unless la
      m = la.size
      la[n-1] = la[n-1] # nil padding
    end.transpose.tap do |ta|
      ta.drop(m).each(&:pop) # remove padding
    end
  end
end
  • 基本戦略は each_slice(n).to_a.transpose
  • しかし、転置 Array#transpose は各要素の大きさが揃っていることが必要。
  • なので転置の前後でパディングの追加と削除を実施する。
    • 転置前は最終行の配列長を他と同じになるよう増やす。(参考:不足分をnilで埋めた配列を取得
    • 転置後は増やした分だけ各行の末端を削る。(※ #compact だと元々あったnilまで消してしまう)
  • 細かい配列操作は破壊的に行い、なるべく新しいインスタンス生成を抑える。
  • 空配列の転置は都合が悪いので、早めに検知してreturnする。(参考:空の配列に対するRubyのメソッドの挙動

無限のリストでも処理できるようにn個のEnumeratorを返すことも考えたが、使い勝手や効率がよくわからないので保留。

module Enumerable
  def lazy_balance(n)
    enum = lazy
    Array.new(n) { |i| enum.drop(i).each_slice(n).map(&:first) }
  end
end

これは Enumerator::Lazy の配列を返しているため、最後に各要素を #force#to_a )すれば最初の例と同じ結果が得られる。

pp (:A..:Z).lazy_balance(7).map(&:force)

pp (:Α..:Ω).lazy_balance(4).map(&:force)

pp [].lazy_balance(3).map(&:force)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング初心者がRailsでサービスを作ってみる

まえがき

プログラミング初心者がRuby on Railsで何か一つサービスを作ってみる。
※職場のエンジニアの方の協力を得つつ…

□前提

▼プログラミングスキル

  • ProgateでRilasを1周している。
  • 「いきなりはじめるPHP入門」で少しだけPHPをやったことがある。

▼ステータス

  • Ruby on Railsで書かれているサービスのプロダクトマネージャーをやっている。
  • 開発ディレクションっぽいことをやっている(けど、コードとかに関する知識がなく、もやもやしている。)

□目標(ゆるく)

  • 自分でサービスを作ってみることで、ディレクションスキルを上げる。
    • 企画〜リリースまでの手順を、実体験をもとに把握する。
    • なんとなくコードが読める。工数がわかる。

□計画(ゆるく)

  • 12月からはじめて4月くらいでリリースできるといいな。
    • 12月
      • ドットインストール見る。
      • Progateやる。
      • チュートリアル見る。

□つまずいたところたち※随時更新

▼12/29
Railsが立ち上がらない。
ActiveRecord::ConnectionNotEstablished No connection pool with ‘primary’ found.
image.png
→これを参考に解決。Railsを始めてsqlite3まわりのエラーで躓いている人たちへ。このコマンドも叩いた。「$ rails db:create」

▼12/30
初期画面以降のプレビュー画面の開き方がわからない。
Progateの教材をCloud9でやろうとした。プレビュー画面の開き方がわからなかった。
→Cloud9でPreview Runing Applicationを選択。開いたページのURLの末尾に/Top/homeなど自分の作ったページのURLを指定して解決。

□ログ※随時更新

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

Railsの新規プロジェクトを指定のRubyバージョンで作成する。

はじめに

久しぶりにRailsの新しいプロジェクトを作成するに当たり、Rubyバージョンを最新のものを利用しようとしてつまずいたので、今後の自分のためにも記事として残しておく。

環境

・rbenvはすでにインストールされているものとする。
https://qiita.com/Alex_mht_code/items/d2db2eba17830e36a5f1
・macOS
Catalina

1. プロジェクトを運用するディレクトリを作成する。

$ mkdir app_name
$ cd app_name

2. Rubyのバージョンを最新にする。

現在使用しているバージョンを確認する

$ rbenv versions

  system
  2.5.1
* 2.6.5

以下のコマンドでインストール可能なバージョンリストが表示される。

$ rbenv -l

インストールしたいバージョンがない場合は以下のコマンドでアップデート
(2019/12/29時点では安定の最新バージョンは2.7.0)

$ brew update
$ rbenv install 2.7.0

インストールが完了すると以下のように表示が増える

$ rbenv versions

  system
  2.5.1
* 2.6.5
  2.7.0

現在はバージョン2.6.5が指定されているので、2.7.0を指定する。
このときコマンドが以下の2パターンあるので使い分けてください。

$ rbenv local 2.7.0 これは現在のディレクトリのプロジェクトのみに適用。
$ rbenv global 2.7.0 その他のプロジェクトのバージョンも全てに適用。

僕は別のアプリを2.6.5で作っているのでlocalで実行。

3. Railsのプロジェクトを作成する。

$ bundle init

Gemfileが生成される。
開いて以下のように編集

# gem rails  → gem rails (コメントアウトを外して保存)
$ bundle install

ここでオプションを付けるかどうかも議論はありますが、プロジェクトが多くなってきてスキルが上がってきたときに考えたほうが理解できると思います。
ざっくりとした説明としてはオプションを付けたほうが容量を節約した運用ができるよということ。

以下のコマンドでプロジェクトを作成。DBの選択などオプションはありますが、シンプルに作っていきます。

rails new .

Gemfileを上書きするか聞かれるのでyを入力し、return。

今回は以下のメッセージが出たので対応。

Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/

こちらを参考に。
https://qiita.com/libertyu/items/1eb74adc817ab8971100
以下のコマンドを実行。

$ brew install yarn

続いて

$ rails webpacker:install

これで準備OK! 以下で起動!

$ rails s

以下にアクセスして起動を確認。
http://localhost:3000/

スクリーンショット 2019-12-30 0.02.58.png

まとめ

一つ一つのコマンドの意味を理解してくことは、とても大切です。
一方で、完璧に理解して進むことは時間がいくらあっても足りません。
今回はコマンド入力しただけで進んだ、次回はそのうちの一つのコマンドについて調べて理解が進んだ状態で進んだというように
毎回毎回少しずつ学習していくことがいいのではないかと考えています。

もし、上記のコマンドでうまく行かなかった際はつまずいた箇所を指摘していただけると幸いです。
共に頑張っていきましょう!

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

ajaxのフォローボタンが反応しないエラー(500 Internal Server Error)

フォローできない 500 Internal Server Error

ajaxのフォローボタンが反応しない。データベースも変更がない。

consolelogのエラー内容

 app/models/user.rb:44
Completed 500 Internal Server Error in 32ms (ActiveRecord: 5.2ms)



ActiveRecord::RecordNotUnique (SQLite3::ConstraintException: UNIQUE constraint failed: relationships.follower_id, relationships.followed_id: INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)):

app/models/user.rb:44:in `follow'
app/controllers/relationships_controller.rb:6:in `create'

対象のコード

class RelationshipsController < ApplicationController
  before_action :user_signed_in?

  def create
    @user = User.find_by(params[:followed_id]) # 対象ユーザーを受け取る
    current_user.follow(@user) # 対象ユーザーをフォローする
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed # Relationshipからidを取得
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

class  User < ApplicationRecord

has_many :posts, dependent:  :destroy

has_many :likes

has_many :comments

has_many :active_relationships, class_name:  "Relationship",

foreign_key:  "follower_id", \# デフォルトではuser_idに紐づけられるので指定

dependent:  :destroy

has_many :passive_relationships, class_name:  "Relationship",

foreign_key:  "followed_id", \# デフォルトではuser_idに紐づけられるので指定

dependent:  :destroy

has_many :following,

through:  :active_relationships, \# active_relationshipsメソッドを呼び出し

source:  :followed  \# 各要素に対してfollowedメソッドを実行

has_many :followers,

through:  :passive_relationships, \# active_relationshipsメソッドを呼び出し

source:  :follower  \# 各要素に対してfollowedメソッドを実行



\# Include default devise modules. Others available are:

\# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable

devise :database_authenticatable, :registerable,

:recoverable, :rememberable, :validatable



validates :name, presence:  true, length: { maximum:  50 }

validates :email, length: { maximum:  255 }

validates :password, length: { minimum:  6 }



def  update\_without\_current_password(params, *options)

params.delete(:current_password)



if params\[:password\].blank? && params\[:password_confirmation\].blank?

params.delete(:password)

params.delete(:password_confirmation)

end



result  =  update_attributes(params, *options)

clean\_up\_passwords

result

end



\# フォロー機能のメソッド



\# ユーザーをフォローする

def  follow(other_user)

following  << other_user

end



\# アンフォロー

def  unfollow(other_user)

active_relationships.find_by(followed_id:  other_user.id).destroy

end



\# 既にフォロー済みのユーザーに含まれていないか確認

def  following?(other_user)

following.include?(other_user)

end

end

原因

どうもこのファイルの中に問題がありそう。
500はコントローラー側に問題があるエラーの様

解決方法

    @user = User.find_by(params[:followed_id]) # 対象ユーザーを受け取る

find_byでなくfindが正解だった。

修正後問題なく動作しフォロー機能挙動。

備考

ちなみにfindメソッドとfind_byメソッドの違いは、、、

findメソッド
  • 検索したいレコードを全て抽出する。
  • マッチするレコードが無い場合はエラーとなる。
find_byメソッド
  • 検索したいレコードの最初に一致した1件だけを抽出
  • マッチするレコードが見当たらない場合nilが返ってくる。
使い分け

find→IDがわかっている場合。
find_byIDが不明且つ別の条件でレコードを検索したい場合。

【参考】
find_byとfindの違い - Qiita

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