20200708のRubyに関する記事は23件です。

gemでインストールしたrailsコマンドがなぜ使えるのか??

はじめに

※windowsのローカル環境下です。※

表題の通り、気になったので調べました。

bin/railsで実行するかrailsで実行するか??


まずは事前知識として、bin/railsで実行するかrailsで実行するか??

結論、どちらを使っても基本問題ないですが、参照元が違います。

binをつけるとプロジェクトディレクトリを参照。

binを付けないとローカル(グローバル)のrailsを参照。

続いて本題。

gemでインストールしたrailsコマンドがなぜ使えるのか??


railsコマンドはローカル環境変数でPath通してるはず。。。

→これはとても浅い勘違い!!

railsは以下のようにgemでインストールしています。

$ gem install rails -v "5.2.3"
$ rails -v
Rails 5.2.3

では、なぜgemでインストールしたrailsでrailsコマンドが通るんだろう??

調べました。

環境変数には以下があります。

C:\Ruby26-x64\bin

ここを確認するとこんな感じ。
$ ls Ruby26-x64/bin
_guard-core*             coderay.bat  nokogiri*     rails.bat  ruby_builtin_dlls/  spring*
_guard-core.bat          erb*         nokogiri.bat  rake*      rubyw.exe*          spring.bat
bundle.cmd               erb.cmd      pry*          rake.bat   sass*               sprockets*
bundler.cmd              gem*         pry.bat       rake.cmd   sass.bat            sprockets.bat
byebug*                  gem.cmd      puma*         rdoc*      sass-convert*       sqlite3.def
byebug.bat               guard*       puma.bat      rdoc.cmd   sass-convert.bat    sqlite3.dll*
chromedriver-helper*     guard.bat    pumactl*      ri*        scss*               thor*
chromedriver-helper.bat  irb*         pumactl.bat   ri.cmd     scss.bat            thor.bat
chromedriver-update*     irb.cmd      rackup*       ridk.cmd   setrbvars.cmd       tilt*
chromedriver-update.bat  listen*      rackup.bat    ridk.ps1   slimrb*             tilt.bat
coderay*                 listen.bat   rails*        ruby.exe*  slimrb.bat          x64-msvcrt-ruby260.dll*

railsがありました。これを参照しているっぽい。

ですが確証が得られないのでさらに調べる。

gemsでインストールした各種ライブラリは以下にあります。

C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems

rails 5.2.3の中身を確認
$ ls Ruby26-x64/lib/ruby/gems/2.6.0/gems/rails-5.2.3
README.md

あれ??どうやって参照している??

whichコマンドを使ってみる。

$ which rails
/c/Ruby26-x64/bin/rails

解決しました◎

やはりRuby直下のbinディレクトリでrailsは参照されていました。

ちなみに、なんで参照されるか。。。

どうやらwhichコマンドは以下のルールらしい。

whichコマンドは、環境変数のPATHに設定されているディレクトリ順に調べ、最初に見つかったコマンドを表示

whichコマンドがこのような参照方法であればコマンドもそうであるはず!!

合点✌

備考:bundle installする際にパスを指定する


指定しないで実行すると前述のとおり、以下にインストールされます。
C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems

パスを指定する方法は以下
bundle install --path vendor/bundle

これでプロジェクト直下のvendor/bundleにインストールできます。

ちなみにrails new時デフォルトでbundle installも実行されますが、Bオプションを付けることで実行スキップできます。

rails new rails_app -B

一度オプションを付けてインストールすれば、以下に設定が保存されて以降は指定不要。

プロジェクト/.bundle/config

---
BUNDLE_PATH: "vendor/bundle"

参考


Rails 4.1以降のコンソールコマンドは必ず bin/ を付けなきゃいけないの?

コマンドの場所を調べるには

railsでbundle installする時にインストールパスを指定

おわりに


日ごろからローカル環境の扱いにはもっと意識を向けていきたいと思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsから入った自分は【attr_accessor】が何か全くわからなかった

Rubyの基礎文法などを見ているとちょくちょく登場するattr_accessorというメソッド。

Ruby on Railsから入った自分は、最初見たときには全く使用用途が分かりませんでした。

そこからいろいろ調べてみるも

  • セッターとゲッターの両方を担うメソッド
  • インスタンス変数の値を参照、変更する

などなど正直理解不能です…

わかっている方には当た有前すぎて何言ってんだこいつ状態かもしれませんが、もし自分と同じところでつまずいた方がいらっしゃれば参考にしていただけると幸いです。

attr_accessorの使用用途

まずは問題のattr_accessorメソッドですが一言でいうとインスタンスに外部から参照、更新可能な属性を持たせるメソッドです。

一つ一つ解説していきます。

インスタンスの値には直接アクセスできない

Railsを使っていると忘れがちなのですが、大前提としてインスタンスの値には直接アクセスすることはできません。

例として以下にHogeクラスを用意しました。インスタンス変数としてnameageの2つがあります。

attr_accessor.rb

class Hoge

  def initialize(name, age)
    @name = name
    @age = age
  end


  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end
end

hoge = Hoge.new("hogehoge",12)
hoge.hello

p hoge.name

この状態でHogeのインスタンスであるhogeの変数であるnameを出力しようとしてみると…

"Hello! I'm hogehoge "
"I'm 12 old"
Traceback (most recent call last):
attr_accessor.rb:33:in `<main>': undefined method `name' for #<Hoge:0x0000000004e68eb8 @name="hogehoge", @age=12> (NoMethodError)

とNoMethodErrorが発生します。

それもそのはず。Hogeクラスにはnameというメソッドが存在していないためです。

こういったインスタンスの値にアクセスできない問題を解決するためにセッターゲッターというメソッドが出てきます。

ゲッター

先ほどのようにインスタンスの値を参照したいときに使われるのがゲッターです。ゲッターの設定方法には2種類あり、メソッドを定義する方法アクセスメソッドを使う方法です。

メソッドを定義する方法は実にシンプルで

class Hoge

  def initialize(name, age)
    @name = name
    @age = age
  end
 
 # インスタンス変数と同じ名前にすることで値の参照を実現させる
  def name
    name = @name
  end

  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end
end

と実行した際にインスタンス変数を引っ張ってくるメソッドを定義します。

ただこれだと少しかっこ悪いのでアクセスメソッドを使った方がまとまりが良いです。

class Hoge
  attr_reader :name

  def initialize(name, age)
    @name = name
    @age = age
  end


  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end
end

attr_readerメソッドを使いことで上のようにインスタンス変数の値を参照できます。この際定義する値はインスタンス変数で定義したものと同じでなければいけません。

これが値を参照するためのゲッターという役割です。

セッター

それでは値の参照ができるようになったので、Railsと同じような感覚でインスタンスの値に別の値を代入してみると…

class Hoge

  attr_reader :name

  def initialize(name, age)
    @name = name
    @age = age
  end

  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end

end

hoge = Hoge.new("hogehoge",12)
hoge.hello
p hoge.name
hoge.name = "fugafuga"


--------------------------------------------------------------------------------

"Hello! I'm hogehoge "
"I'm 12 old"
"hogehoge"
Traceback (most recent call last):
attr_accessor.rb:37:in `<main>': undefined method `name=' for #<Hoge:0x0000000004fbb040 @name="hogehoge", @age=12> (NoMethodError)
Did you mean?  name

とまたもや怒られてしまいます。

これはゲッターでは値を参照することができても、更新する機能は含まれていないので新しい値を代入できないためです。

この問題を解決するために使われるのがセッターと呼ばれるものです。

セッターもメソッドをそのまま定義する方法とアクセサメソッドを使う方法の2種類があります。

def name=(name)
    @name = name
end

メソッドの後に=を付けるとセッターになります。
内容はシンプルで受け取った引数をインスタンス変数に反映するだけです。

アクセサメソッドを使う方法。

class Hoge

  attr_reader :name
  attr_writer :name

  def initialize(name, age)
    @name = name
    @age = age
  end

  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end
end

アクセサメソッドはattr_writerを使います。

hoge = Hoge.new("hogehoge",12)
hoge.hello
p hoge.name
hoge.name = "fugafuga"
hoge.hello

------------------------------------------------------

"Hello! I'm hogehoge "
"I'm 12 old"
"hogehoge"
"Hello! I'm fugafuga "
"I'm 12 old"

と確かにnameの値が更新されています。

attr_accessor

いよいよattr_accessorの解説ですが上記のゲッターとセッターの両方を担うのがこのメソッドの役割ということになります。

つまりattr_reader, attr_writerの2種類を書かなくても

class Hoge
  # この一行でゲッターとセッターの役割を果たしている
  attr_accessor :name

  def initialize(name, age)
    @name = name
    @age = age
  end

  def hello
    p "Hello! I'm #{@name} "
    p "I'm #{@age} old"
  end
end

hoge = Hoge.new("hogehoge",12)
hoge.hello
p hoge.name
hoge.name = "fugafuga"
hoge.hello

----------------------------------------------------------

Hello! I'm hogehoge "
"I'm 12 old"
"hogehoge"
"Hello! I'm fugafuga "
"I'm 12 old"

とかなりシンプルに書くことができます。

ここまで読んでいて不思議に思ったかもしれませんが、Railsでは通常このアクセサメソッドは出てきません。

ではなぜメソッドのようにテーブルの値を参照、更新できているのかというと、ActiveRecord::Baseで自動機的に設定されているからです。

よければそのことについても記事を書いているのでご覧下さい
>> rails db:migrateとモデルという存在について

ここまでがattr_accessorの解説になります。

まとめ

メソッド 機能 役割
attr_reader ゲッター インスタンス変数の値を参照できるようになる
attr_writer セッター  インスタンス変数の値を更新できるようになる
attr_accessor ゲッター・セッター  インスタンス変数の値を参照、更新できるようになる

Ruby on Railsは非常に便利なフレームワークですが、時々Rubyの基礎を勉強し直さないといけないなと強く思いました…

皆様も是非一度この辺りの内容を自分でコードを書いてみることをおすすめします!

それではー

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

☆初投稿:TECH CAMP学習、個人アプリ作成①、駆け出しエンジニア

TECH CAMPに通い出して

初めて投稿させていただきました。
TECH CAMPの夜間コースに通い出して3ヶ月が経ちました。
カリキュラムではわからないことも沢山あり、その都度Googleで調べたり、メンターさんに質問したりと、苦労も沢山ありました。エラーを繰り返しながらも、無事に動いたときというのは何とも言えない喜びもありました。

諸々、何とかこなし、いよいよ個人アプリ開発となりました。

正直、現在も試行錯誤の連続でありますが、やはり自分のやっていることというのは、時間がたつと忘れてしまうこともありますので、自分が苦労して解決したことなどは記録していった方がよいと考え、投稿させていただきました。

まだまだ不慣れであり、かつ知識も未熟なため、書いてあることが間違っていることもあるかもしれませんが、はっきりいって投稿することに意味があるのだと思いましたので、間違っているところははっきり指摘していただけたら嬉しいです。

個人アプリの作成時、viewを表示させようとしたのですが
まずは以下のエラーがありました。

image.png

原因はhamlの導入ができていませんでした。
なのでGemfileに

『gem 'haml-rails'』

を入力して
ターミナルにて

『bundle install』

を実行、その後すでにあるerbをhamlに変換

『rails haml:erb2haml』

すると「would you like〜」と出てくるので「y」を入力
これでエラーは解決しました。

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

Railsでdeviseを追加したのに反映されなかったときの対処法

dockerで環境構築を行いgemの追加をした際にエラーが発生しましたので
備忘録として情報共有しようと思います!

開発環境

docker
rails (5.2.0)
ruby (2.7.1)
mysql (5.7)
nginx
puma

※docker-compose buildでgemの更新をしますが時間がかかるので下記の記事を参考に
gemの更新を素早くする設定をしています。
 https://qiita.com/neko-neko/items/abe912eba9c113fd527e

エラー内容

Gemfileでdeviseのgemを追加し、docker-compose run --rm rails bundle installをしたところ以下のエラーが発生

~ RailsApp % docker-compose run --rm rails bundle install              #bundle installを実行
Starting railsapp_db_1 ... done
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Bundler could not find compatible versions for gem "railties":         # エラー発生
  In snapshot (Gemfile.lock):
    railties (= 5.2.4.3)

  In Gemfile:
    devise (= 4.1.0) was resolved to 4.1.0, which depends on
      railties (< 5.1, >= 4.1.0)

    rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on
      railties (= 5.2.4.3)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

何が原因なのかわからなかったが、とりあえず「bundle updateしたら解決するかもよ」と書いてあったので
docker-compose run --rm rails bundle updateを実行

↓↓↓↓↓↓↓↓

~ RailsApp % docker-compose run --rm rails bundle update           #bundle updateを実行
Starting railsapp_db_1 ... done
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...........................
Bundler could not find compatible versions for gem "railties":      # 同じエラーが発生
  In snapshot (Gemfile.lock):
    railties (= 5.2.4.3)

  In Gemfile:
    devise (= 4.1.0) was resolved to 4.1.0, which depends on
      railties (< 5.1, >= 4.1.0)

    rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on
      railties (= 5.2.4.3)

ダメでしたね・・・

原因

結論から申し上げるとdeviseというgemがlistにないことがエラーの原因だったみたいです。


下記の記事を参考にさせていただきました。
https://qiita.com/hatorijobs/items/2928e152f22d009b07d0

こちらの記事をそのまま実行すれば解決しますが、dockerで作業している方は若干、コマンドが
異なりますのでこちらで説明させていただこうと思います。

対処方法

以下の順に操作をしていきます。

1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。
2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。
3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。
4. Gemfileに**gem 'devise','4.7.2'を記述する
5. docker-compose run --rm rails bundle updateを実行

順番に見ていきましょう。
↓↓↓↓↓↓↓↓

1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。

~ RailsApp % docker-compose exec app gem list

*** LOCAL GEMS ***

actioncable (5.2.4.3, 5.2.2)
actionmailer (5.2.4.3, 5.2.2)
actionpack (5.2.4.3, 5.2.2)
actionview (5.2.4.3, 5.2.2)
actionjob (5.2.4.3, 5.2.2)

~省略~

web-console (3.7.0)

2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。

~ RailsApp % docker-compose exec app gem install devise
Fetching warden-1.2.8.gem
Fetching bcrypt-3.1.13.gem

~省略~

Successfully installed devise-4.7.2
5 gems installed

3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。

~ RailsApp % docker-compose exec app gem list

*** LOCAL GEMS ***

actioncable (5.2.4.3, 5.2.2)
actionmailer (5.2.4.3, 5.2.2)
actionpack (5.2.4.3, 5.2.2)
actionview (5.2.4.3, 5.2.2)
actionjob (5.2.4.3, 5.2.2)

~省略~

devise (4.7.2)

~省略~

web-console (3.7.0)

4. Gemfileにgem 'devise','4.7.2'を記述する

Gemfile.
source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.2.1'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'
# Use Puma as the app server

~省略~

#ログイン機能の追加
gem 'devise','4.7.2'

~省略~

5. docker-compose run --rm rails bundle updateを実行

~ RailsApp % docker-compose run --rm rails bundle update

これでエラーが出なければ我々の勝ちです

気づいたこと

エラー対応している際に気づいたことがいくつかあったので共有させていただきます

docker-compose exec app gem install deviseを実行すると最新版のgemがインストールされます。
最新版でも特に問題ないと判断したため、バージョン指定する方法の説明は省略させていただきました。
※逆に方法がわかる方は教えてください!

ちなみにdeviseの全バージョン履歴が掲載されたサイトを見つけましたのでURLを連携させていただきます。
https://rubygems.org/gems/devise/versions
※2020.06.10時点での最新バージョンは4.7.2


[任意のサービス名]とはdocker-compose.ymlで指定しているサービス名のことです。
任意で決めることができますが、どのサイトでもdbとかappとかwebて設定している方が多い印象です。

※わからないて方は、「docker-compose.yml サービス名」で調べると記事がヒットすると思います。

終わりに

Railsやdockerについては今後もアウトプットのためにエラーに関する記事等を定期的に投稿していこうと思っております。
また、初めての投稿で至らない点があったかと思いますが、最後まで読んでいただきありがとうございました!!!

以上

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

Railsでdeviseを追加したいのにbundle installができない

dockerで環境構築を行いgemの追加をした際にエラーが発生しましたので
備忘録として情報共有しようと思います!

開発環境

docker
rails (5.2.0)
ruby (2.7.1)
mysql (5.7)
nginx
puma

※docker-compose buildでgemの更新をしますが時間がかかるので下記の記事を参考に
gemの更新を素早くする設定をしています。
 https://qiita.com/neko-neko/items/abe912eba9c113fd527e

エラー内容

Gemfileでdeviseのgemを追加し、docker-compose run --rm rails bundle installをしたところ以下のエラーが発生

~ RailsApp % docker-compose run --rm rails bundle install              
Starting railsapp_db_1 ... done
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Bundler could not find compatible versions for gem "railties":         
  In snapshot (Gemfile.lock):
    railties (= 5.2.4.3)

  In Gemfile:
    devise (= 4.1.0) was resolved to 4.1.0, which depends on
      railties (< 5.1, >= 4.1.0)

    rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on
      railties (= 5.2.4.3)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

何が原因なのかわからなかったが、とりあえず「bundle updateしたら解決するかもよ」と書いてあったので
docker-compose run --rm rails bundle updateを実行

↓↓↓↓↓↓↓↓

~ RailsApp % docker-compose run --rm rails bundle update           
Starting railsapp_db_1 ... done
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...........................
Bundler could not find compatible versions for gem "railties":      
  In snapshot (Gemfile.lock):
    railties (= 5.2.4.3)

  In Gemfile:
    devise (= 4.1.0) was resolved to 4.1.0, which depends on
      railties (< 5.1, >= 4.1.0)

    rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on
      railties (= 5.2.4.3)

ダメでしたね・・・

原因

結論から申し上げるとdeviseというgemがlistにないことがエラーの原因だったみたいです。


下記の記事を参考にさせていただきました。
https://qiita.com/hatorijobs/items/2928e152f22d009b07d0

こちらの記事をそのまま実行すれば解決しますが、dockerで作業している方は若干、コマンドが
異なりますのでこちらで説明させていただこうと思います。

対処方法

以下の順に操作をしていきます。

1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。

2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。

3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。

4. Gemfileに**gem 'devise','4.7.2'を記述する

5. docker-compose run --rm rails bundle updateを実行

順番に見ていきましょう。
↓↓↓↓↓↓↓↓

1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。

~ RailsApp % docker-compose exec app gem list

*** LOCAL GEMS ***

actioncable (5.2.4.3, 5.2.2)
actionmailer (5.2.4.3, 5.2.2)
actionpack (5.2.4.3, 5.2.2)
actionview (5.2.4.3, 5.2.2)
actionjob (5.2.4.3, 5.2.2)

~省略~

web-console (3.7.0)

2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。

~ RailsApp % docker-compose exec app gem install devise
Fetching warden-1.2.8.gem
Fetching bcrypt-3.1.13.gem

~省略~

Successfully installed devise-4.7.2
5 gems installed

3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。

~ RailsApp % docker-compose exec app gem list

*** LOCAL GEMS ***

actioncable (5.2.4.3, 5.2.2)
actionmailer (5.2.4.3, 5.2.2)
actionpack (5.2.4.3, 5.2.2)
actionview (5.2.4.3, 5.2.2)
actionjob (5.2.4.3, 5.2.2)

~省略~

devise (4.7.2)

~省略~

web-console (3.7.0)

4. Gemfileにgem 'devise','4.7.2'を記述する

Gemfile.
source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.2.1'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'
# Use Puma as the app server

~省略~

#ログイン機能の追加
gem 'devise','4.7.2'

~省略~

5. docker-compose run --rm rails bundle updateを実行

~ RailsApp % docker-compose run --rm rails bundle update

これでエラーが出なければ我々の勝ちです

気づいたこと

エラー対応している際に気づいたことがいくつかあったので共有させていただきます

docker-compose exec app gem install deviseを実行すると最新版のgemがインストールされます。
最新版でも特に問題ないと判断したため、バージョン指定する方法の説明は省略させていただきました。
※逆に方法がわかる方は教えてください!

ちなみにdeviseの全バージョン履歴が掲載されたサイトを見つけましたのでURLを連携させていただきます。
https://rubygems.org/gems/devise/versions
※2020.06.10時点での最新バージョンは4.7.2


[任意のサービス名]とはdocker-compose.ymlで指定しているサービス名のことです。
任意で決めることができますが、どのサイトでもdbとかappとかwebて設定している方が多い印象です。

※わからないて方は、「docker-compose.yml サービス名」で調べると記事がヒットすると思います。

終わりに

Railsやdockerについては今後もアウトプットのためにエラーに関する記事等を定期的に投稿していこうと思っております。
また、初めての投稿で至らない点があったかと思いますが、最後まで読んでいただきありがとうございました!!!

以上

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

【Ruby】Ubuntu での Ruby 開発環境構築

はじめに

2020/7/7 時点で Ruby の環境構築をしてみたので、書き起こします。けっこうたくさんの方が記事にしていらっしゃいますが、自分でアウトプットするとそれだけでも勉強になるので。

導入環境

  • Ubuntu 20.04 LTS (64bit)
    • Windows 10 Virtual Box内にインストールしています。

(注意)同じような Ruby バージョン管理ツールに、「RVM」がありますが、rbenv と RVM は非互換なので、「RVM」が入っていない環境を準備します。

入れるもの

複数のRubyバージョンの管理ができる rbenv というツールも一緒に入れます。複数のRubyバージョンを入れられるだけでなく、プロジェクト毎に使用するRubyのバージョンを指定することができるようになります。
 また、rbenv のオプション扱い(?)ですが、ruby-build も導入します。

  • rbenv 1.1.2-30-gc879cb0
    • ruby-build
  • Ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

※どちらも、2020/7/7 時点で最新の安定バージョン、だと思います。

大きな流れ

流れはこんな感じです。
 0. git をインストールしておく。
 1. 事前に必要なツール、パッケージを導入する。
 2. rbenv のインストールと環境設定
 3. ruby-build のインストール
 4. Rubyのインストール

手順0. git のインストール

git 自体は環境構築とは直接関係がないのですが、rbenv, ruby-build のインストールの際、github からダウンロードします。このため、git をあらかじめ入れておいてください。
インストール方法は、こちら に書かれていますが、apt-getコマンド一つで完了です。
※必要に応じて、sudo を頭につけてください。

apt-get install git

手順1. 必要なツール、パッケージの導入

詳しくはこちらに書かれていますが、英語なので、、、
rbenv+ruby-build で必要な Ruby のバージョンをダウンロード、インストールする際、環境の違いでコンパイルエラーが起こったりする場合がある、とのことで、少なくとも下記パッケージは事前に入れておいた方が良いよ、ということです。

一気に入れる場合は、下の長いコマンドをコピペして実行してください。
※必要に応じて、sudo を頭につけてください。

apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev

「libgdbm6」が使えない環境の場合は、「libgdbm5」でやってみて、とのことです。
それぞれのパッケージが何か、については、自分でも調べてみてもよく分からないものが多い、というより、必要になって使う場面にならないと分からなそうなので、興味がある方はそれぞれ調べてみてください。

【補足】事前に必要なパッケージの一覧(自分なりに調べてみた)

※ あくまで2020/7/7 時点で ruby-build の説明に記載のあったものです。
パッケージ名 これって何? 自分の環境に個別に入れてみたときの反応
autoconf configure スクリプトの自動生成ツール
bison パーサジェネレータの一種。コンパイルのときに使われるみたい
build-essential 名前の通り、開発に必要なビルドツール一式が入ってるみたい。
libssl-dev SSL関連の開発用ツールキット
libyaml-dev YAML関連の開発用ライブラリ
libreadline6-dev GNU readline のライブラリらしいが、何の機能かよく分からない・・・ libreadline-dev をすすめられ、libncurses-dev が依存でインストールされました
zlib1g-dev 圧縮(gzip, pkzip )に関するライブラリ  
libncurses5-dev ncurses関連の開発用ライブラリ。端末に依存しない形式でテキストユーザインタフェース (TUI) を作成するためのAPIを提供するそうです
libffi-dev Foreign Function Interface に関するライブラリ。他の言語で書かれたメソッドを呼び出す際とかに使われるんだと思います。
libgdbm6 GNU dbm データベースルーチン (ランタイム版)
libgdbm-dev GNU dbm データベースルーチン (開発用ファイル)
libdb-dev Berkeley Database Libraries [development] → libdb5.3-dev が追加でインストールされました。

手順2. rbenv のインストールと環境設定

2-1. rbenv のインストール

rbenv のインストールは、git clone を使って github からダウンロードし、ホームディレクトリの.rbenvフォルダーに設置します。

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv

2-2. rbenv の環境設定1 パスを通す

rbenv の環境設定として、~/.rbenv/binへのパスを通します。公式だと、Ubuntu desktop 環境の場合は、.bashrc にパス設定を追加するように、と記載されています。ので、下のようにコマンド実行すればOKです。

echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

【補足】.bashrcではなく、.profile に書く場合

個人的に .bashrc にパスの設定を書く、というのがなんだか筋が悪いような気がしています。環境変数を書くなら .profile か、.bash_profile じゃないのかなぁ、と思うので、自分の環境では .profile に書き込みました。

.profile にパス設定を記述する場合は、.profile をエディタで開いて、下記コードを追記して下さい。

if [ -d "$HOME/.rbenv/bin" ] ; then
    PATH="$HOME/.rbenv/bin:$PATH"
fi


2-3. rbenv の環境設定2 rbenv init - を .bashrc に入れる。

rbenv init - を .bashrc 内で実行するように、下記コマンドを実行します。

echo 'eval "$(rbenv init -)"' >> ~/.bashrc

その後、設定を反映させます。

source ~/.bashrc

【補足】rbenv init - を .bashrc に入れることについて

rbenv init - コマンドは、shimsディレクトリの再作成やら、rehash処理なんかをしているのですが、こちらのコマンド内でも $PATH に shims へのパスを追加しています。なので、.bashrc に入れるのはなんだか気持ち悪いのですが、他にもいろいろ処理をしているので、こちらは公式の指示に従いました。

※ .bashrc に入れると、なにか変更して source ~/.bashrc で設定反映する度に、パスが2重、3重と追加されてしまうんですよね、、、かといって.bashrc を変更したのに、source ~/.profileするのもなんか変ですし。


手順3. ruby-build のインストール

ruby-build をインストールします。ruby-build も github 上に公開されていますので、rbenv と同じように、git clone でダウンロードします。設置する場所は、~/.rbenv/plugins/ ディレクトリの下です。

git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

これで、rbenv install コマンドが使えるようになります。

手順4. rubyのインストール

rbenv install コマンドで、ruby の各バージョンのインストールができるようになっています。

まず、rbenv install -l で、インストールできる安定バージョンを確認します。

$ rbenv install -l
2.5.8
2.6.6
2.7.1
jruby-9.2.12.0
maglev-1.0.0
mruby-2.1.1
rbx-5.0
truffleruby-20.1.0

Only latest stable releases for each Ruby implementation are shown.
Use 'rbenv install --list-all' to show all local versions.

通常版Ruby の 2020/7/7 時点での最新安定版は 2.7.1 のようなので、そちらをインストールする場合は、バージョンを指定します。

rbenv install 2.7.1

インストール後、通常使う ruby のバージョンとして設定するには、rbenv global を使います。

rbenv global 2.7.1

最後に、インストールしたrubyのバージョンをshimsに反映させるために、rbenv rehash を実行します。今後、ruby の別バージョンを入れたり、gem を追加したりした時は、rbenv rehash を行った方がいいそうです。

rbenv rehash

エラーが出ていなければ、これで ruby の実行ができるようになっています。
'ruby -v' でバージョンの確認ができます。

$ ruby -v
Ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

また、irbを使って "Hello, World!" を表示させてみます。

$ irb
irb(main):001:0> puts "Hello, World!"
Hello, World!
=> nil
irb(main):002:0> 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでhtml.erbのclassを条件付きで追加する方法

概容

下記のクラスに「ある条件」の時だけクラスを追加したいとする。

<div class="container">
</div>

例えば今回はusers_controllerの時だけクラスを追加するとする。
その場合の条件式は下記のようになる。

<div class="container<%= ' user-container' if controller_name = 'users' %>">
</div>

※注意

追加したいクラス名の先頭文字の前はスペースを開けること。

正しい: <%= ' user-container' if・・・
間違い: <%= 'user-container' if・・・

このような条件付きでclassを追加することによってその条件下のみでCSSでスタイルの変更や上書きができるようになるのでぜひ覚えておきたい。

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

初心者が書く!macOS catalina に postgreSQL をインストールしてrails6.0.3.2で使えるようにするまで

プログラミングとmacの初心者です。
わからないなりに、色々調べて非常に時間を要したので
自分の覚書として念の為に書いておこうと思った次第ですが、
もし間違った記載など有りましたら玄人の方にご指摘いただけると本当に助かりますm(-_-)m
玄人以外の方も、出来ないなどご相談があれば、可能な限り対応します。※可能な限りですよ^^;
宜しくおねがいします。


まずはhomebrewでpostgresqlをインストールして初期設定

ターミナルで以下を実行。場所はどこでも良いみたいです。
私は前に作ったprojectsフォルダで実行しました。
この後の作業は特に記載がなければ全てターミナルで行っています。

brew install postgresql

そして以下が初期設定らしいです。

initdb /usr/local/var/postgres -E utf8

起動して設定確認

まずは起動。

brew services start postgresql

以下でEncodingがUTF8になってればOKだそうです。ひょっとしたら初期設定しなくてもなってるのかも??

psql -l

そして終了。

brew services stop postgresql

ここはpostgreを使いやすくする設定(だそうです。)

ホームフォルダ直下にある .zshrcをvscodeで編集しました。
具体的には以下を追記。

export PATH="/usr/local/opt/postgres:$PATH"
export PGDATA="/usr/local/var/postgres"

そしてターミナルから以下を実行すればpostgreが起動します。

source ~/.zshrc #変更した設定を適応。
postgre

LOG: 〜
LOG: 〜
LOG: 〜
みたいな画面ならOKだそうです。
control+cで止まります。

ここからはrailsの設定です。

rails 6.0.3.2 new コマンドで好きなアプリを適当に作って下さい。ただしもちろん
-d postgresql を忘れずに^^

database.ymlの設定とデータベース作成権限を持ったuserの作り方
※<任意のusername>は好きな名前です。私は今回、rootにしました。

# database.yml内
default: &default
  adapter: postgresql
  encoding: utf8 #utf8に変更
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <任意のusername> #追加
  password: #追加
  host: localhost #追加
# ターミナル
createuser <任意のusername> -d
# 消すとときは dropuser <任意のusername>

ここまで終わったらrails db:create (もちろん任意のフォルダで)でできると思います。

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

Kinx ライブラリ - パーサ・コンビネータ(その2)

Kinx ライブラリ - パーサ・コンビネータ(その2)

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。今回もパーサ・コンビネータです。

前回 Kinx ライブラリ - パーサ・コンビネータ(その1) で AST を作るところまで実施しました。

今回はこれを解釈して実行させます。最後には JIT コンパイルして実行するところまでいきます。

その前に

おさらい

前回の最終的なプログラムを再確認しておきましょう。

using Parsek;

function makeAST(first, rest) {
    var expr = first;
    for (var i = 0, l = rest.length(); i < l; ++i) {
        expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] };
    }
    return expr;
}

var $ = new Parsek();
var number = $.regex(/[1-9][0-9]*|[0-9]/).map(Integer.parseInt);
var addsub = $.oneOf("+-");
var muldiv = $.oneOf("*/%");
var lbr = $.string("(");
var rbr = $.string(")");

var expression;
var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr).map(&(value) => value[1])));
var term = $.seqMap(factor, $.seq(muldiv, factor).many(), makeAST);
expression = $.seqMap(term, $.seq(addsub, term).many(), makeAST);

// test
System.println(expression.parseAll("1+2*3+2*(14-2)").value.toJsonString(true));

これです。これについて 1 つだけ問題点を直しておきましょう。問題というほどではないですが。

実はこのパーサー、空白文字があると失敗します。当然ですね、空白を解釈するところが無いので、空白に遭遇すると想定した文字でないため「文法あってないぜ!」と自信満々に答えてくるのです。

空白文字の読み飛ばし処理

普通のプログラミング言語では、トークンの切れ目が任意の数の空白文字になっており、空白は読み飛ばす対象であることが一般的です。そこで空白文字は読み飛ばすようにしましょう。そこで使うのが以下の 2 つです。

  • $.optWhitespace は 0 個以上の任意の空白文字を解釈するパーサーです。具体的には $.regex(/\s*/) と同じです。したがって、空白、改行(CR/LF)、タブにマッチします。尚、$.whitespace というのもあり、こちらは $.regex(/\s+/) で 1 個以上の空白文字にマッチします。
  • parser.skip(anotherParser)parser にマッチした後 anotherParser にマッチするか試し、マッチしたら parser の結果を返すといった動作をします。つまり、anotherParser のマッチ結果をスキップします。

この 2 つを使って、.skip($.optWhitespace) とすることで特定のパーサー・マッチングの後で空白文字を読み飛ばすことが可能になります。

ここでは以下のように lexeme() という関数を用意し、空白文字を読み飛ばす処理を追加しましょう。ignore は無視するものが増えたときにここに追加すればよいのと、最初の出だしでも空白文字のスキップが必要なので定義しておきます。

var ignore = $.optWhitespace;
var lexeme = &(p) => p.skip(ignore);

この lexeme() 関数は、トークンの切れ目にあれば良いので、numberaddsubmuldivlbrrbr の最小単位のパーサーにあれば良いですね。最初のプログラムを書き換えると次のようになります。

using Parsek;

function makeAST(first, rest) {
    var expr = first;
    for (var i = 0, l = rest.length(); i < l; ++i) {
        expr = { lhs: expr, op: rest[i][0], rhs: rest[i][1] };
    }
    return expr;
}

var $ = new Parsek();
var ignore = $.optWhitespace;
var lexeme = &(p) => p.skip(ignore);

var number = lexeme($.regex(/[1-9][0-9]*|[0-9]/)).map(Integer.parseInt);
var addsub = lexeme($.oneOf("+-"));
var muldiv = lexeme($.oneOf("*/%"));
var lbr = lexeme($.string("("));
var rbr = lexeme($.string(")"));

var expression;
var factor = $.lazy(&() => $.alt(number, $.seq(lbr, expression, rbr).map(&(value) => value[1])));
var term = $.seqMap(factor, $.seq(muldiv, factor).many(), makeAST);
expression = $.seqMap(term, $.seq(addsub, term).many(), makeAST);

// test
System.println(expression.parseAll("1+2*3+2*(14-2)").value.toJsonString(true));
System.println(ignore.then(expression).parseAll("  1 + 2 * 3 + 2 * ( 14 - 2 )  ").value.toJsonString(true));
/* Both results are the same.
{
    "lhs": {
        "lhs": 1,
        "op": "+",
        "rhs": {
            "lhs": 2,
            "op": "*",
            "rhs": 3
        }
    },
    "op": "+",
    "rhs": {
        "lhs": 2,
        "op": "*",
        "rhs": {
            "lhs": 14,
            "op": "-",
            "rhs": 2
        }
    }
}
*/

どちらも同じ結果が返ります。

最初の ignore.then() が先頭の空白を無視する部分です。parser.then(anotherParser).skip() とは違って anotherParser のほうを返します。これによって、parser のほうが無視されます。

では、本題に入りましょう。

インタプリタ

まず JIT の前にインタプリタを実装してみましょう。こっちのほうが簡単です。インタプリタは AST をトラバースしながら逐次実行していきます。左辺を評価し、右辺を評価し、その結果で演算すれば OK。Interpreter クラスは以下のようになります。

class Interpreter(opts_) {
    private sequence(r, op, lhs, rhs) {
        return if (!opts_.enableSequence);
        System.println("%d %s %d -> %d" % lhs % op % rhs % r);
    }
    public eval(ast) {
        var lhs = ast.lhs.isObject ? eval(ast.lhs) : ast.lhs;
        var rhs = ast.rhs.isObject ? eval(ast.rhs) : ast.rhs;
        var r = 0;
        switch (ast.op) {
        case '+':
            r = lhs + rhs;
            break;
        case '-':
            r = lhs - rhs;
            break;
        case '*':
            r = lhs * rhs;
            break;
        case '/':
            r = lhs / rhs;
            break;
        case '%':
            r = lhs % rhs;
            break;
        default:
            throw RuntimeException('Invalid operator');
        }
        sequence(r, ast.op, lhs, rhs);
        return r;
    }
}

計算過程も見えるようにしてみました(コンストラクタに { enableSequence: true } を渡すと計算過程を表示します)。こんな感じで実行してみましょう。

System.println(
    "Result = ",
    new Interpreter({ enableSequence: true })
        .eval(ignore.then(expression)
                    .parseAll("  1 + 2 * 3 + 2 * ( 14 - 2 )  ").value)
);
/*
2 * 3 -> 6
1 + 6 -> 7
14 - 2 -> 12
2 * 12 -> 24
7 + 24 -> 31
Result = 31
*/

ちゃんと優先順位に従って計算されてますね! AST が優先順位の通りに構築されているので当然です。別の式でもやってみましょう。

System.println(
    "Result = ",
    new Interpreter({ enableSequence: true })
        .eval(ignore.then(expression)
                    .parseAll("(( 123 ) * 2 * 4 - 3 * ( 12 + 10 )) % 100").value)
);
/*
123 * 2 -> 246
246 * 4 -> 984
12 + 10 -> 22
3 * 22 -> 66
984 - 66 -> 918
918 % 100 -> 18
Result = 18
*/

あってますねー。

JIT コンパイル

お待ちかねの JIT です。ただし、JIT 技術自体は素晴らしいのですが、正直このレベルの計算量(ループも条件分岐もない)だと素直に上記のインタプリタのほうが速いのでご了承ください。インタプリタと同じルートでコンパイルしてるのと、インタプリタ自体が大した事やってないので、JITコンパイル時間がインタプリタ時間(+α)みたいなものになってしまってます。むむ。

あくまで技術ベースということで。もっと速さに差が出る例題にすればよかったな。今度 ここ の記事を参考にして Brainf*ck を題材に書き直すか。

さて、Interpreter では直接計算式を実行してましたが、計算を実行する代わりに同等のマシン語コードを出力するように書き換えれば OK です。JIT ライブラリ を見ながら、マシン語コードに変換する Compiler クラスは以下のようになるでしょう。ちょっと長いですが、そのまま載せます。今回も コンストラクタに { enableListing: true } とすることで中間形式のコンパイルコードを出力できます。

class Compiler(opts_) {
    var regs_, regsLen_;
    enum { MOV, BOP, DIVMOD, RET }
    private initialize() {
        regs_ = [
            // Jit.R0 and Jit.R1 is used as a work register when it is division.
            { reg: Jit.R2, used: false, name: "R2" },
            { reg: Jit.R3, used: false, name: "R3" },
            { reg: Jit.R4, used: false, name: "R4" },
            { reg: Jit.R5, used: false, name: "R5" },
            { reg: Jit.S0, used: false, name: "S0" },
            { reg: Jit.S1, used: false, name: "S1" },
            { reg: Jit.S2, used: false, name: "S2" },
            { reg: Jit.S3, used: false, name: "S3" },
            { reg: Jit.S4, used: false, name: "S4" },
            { reg: Jit.S5, used: false, name: "S5" },
        ];
        regsLen_ = regs_.length();
    }
    private listing(type, op, dst, op1, op2) {
        return if (!opts_.enableListing);
        switch (type) {
        case MOV:
            System.println("%s <- %s" % dst % op1);
            break;
        case BOP:
            System.println("%s <- %s %s %s" % dst % op1 % op % op2);
            break;
        case DIVMOD:
            System.println("R0 <- %s" % op1);
            System.println("R1 <- %s" % op2);
            System.println("%s <- R0 %s R1" % dst % op);
            break;
        case RET:
            System.println("ret %s" % dst);
            break;
        }
    }
    private getReg() {
        for (var i = 0; i < regsLen_; ++i) {
            if (!regs_[i].used) {
                regs_[i].used = true;
                return i;
            }
        }
        throw RuntimeException("Not enough register");
    }
    private releaseReg(i) {
        regs_[i].used = false;
    }
    private compileLeaf(c, leaf) {
        var r = getReg();
        c.mov(regs_[r].reg, Jit.IMM(leaf));
        listing(MOV, null, regs_[r].name, leaf);
        return r;
    }
    private compileNode(c, ast) {
        var rl = ast.lhs.isObject ? compileNode(c, ast.lhs) : compileLeaf(c, ast.lhs);
        var rr = ast.rhs.isObject ? compileNode(c, ast.rhs) : compileLeaf(c, ast.rhs);
        var r = getReg();
        switch (ast.op) {
        case '+':
            c.add(regs_[r].reg, regs_[rl].reg, regs_[rr].reg);
            listing(BOP, ast.op, regs_[r].name, regs_[rl].name, regs_[rr].name);
            break;
        case '-':
            c.sub(regs_[r].reg, regs_[rl].reg, regs_[rr].reg);
            listing(BOP, ast.op, regs_[r].name, regs_[rl].name, regs_[rr].name);
            break;
        case '*':
            c.mul(regs_[r].reg, regs_[rl].reg, regs_[rr].reg);
            listing(BOP, ast.op, regs_[r].name, regs_[rl].name, regs_[rr].name);
            break;
        case '/':
            c.mov(Jit.R0, regs_[rl].reg);
            c.mov(Jit.R1, regs_[rr].reg);
            c.sdiv();
            c.mov(regs_[r].reg, Jit.R0);
            listing(DIVMOD, ast.op, regs_[r].name, regs_[rl].name, regs_[rr].name);
            break;
        case '%':
            c.mov(Jit.R0, regs_[rl].reg);
            c.mov(Jit.R1, regs_[rr].reg);
            c.sdivmod();
            c.mov(regs_[r].reg, Jit.R1);
            listing(DIVMOD, ast.op, regs_[r].name, regs_[rl].name, regs_[rr].name);
            break;
        default:
            throw RuntimeException('Invalid operator');
        }
        releaseReg(rl);
        releaseReg(rr);
        return r;
    }
    public compile(ast) {
        var c = new Jit.Compiler();
        c.enter();
        var r = compileNode(c, ast);
        c.ret(regs_[r].reg);
        listing(RET, null, regs_[r].name);
        return c.generate();
    }
    public run(ast) {
        var code = compile(ast);
        return code.run();
    }
}

レジスタは R2R5S0S5 が利用可能です。R0R1 は除算で使う必要があるのでそのために取っておきます。レジスタの割り当て方は「使うときに予約して、使い終わったら返す」です。

尚、R 系のレジスタは関数呼び出し後に破壊される可能性がありますが、今回は演算するだけで関数呼び出しをしないので S 系と同様の扱いで良いでしょう。使えるレジスタを使い切るとエラーしますが、中間値の多くは短命なので問題ないでしょう。もし保存すべき値が増えたら、メモリへ退避させるコードを出します。また、Kinx の JIT ライブラリにはスタック上にローカル変数領域を持てるので、足りなくなったらそこに割り当てる、というのが一番楽です。

本格的にレジスタ割り付けとかを考えるとかなり奥が深いですが、JIT の場合は 最適化にはあまり時間をかけない というのがセオリーですので、この程度で十分でしょう。なぜならば、最適化の時間がもったいないからです。事前コンパイル(AOT Compilation)のときはコンパイル時間は気にせずに実行時に速ければ速いだけよいので最適化をガンガンしますが、JIT ではコンパイル時間も実行時間のうちなのでそうもいかないのです。

では早速計算させてみます。using Jit; を忘れずに。

using Parsek;
using Jit;

/* 省略 */
System.println(
    "Result = ",
    new Compiler({ enableListing: true })
        .run(ignore.then(expression)
                   .parseAll("  1 + 2 * 3 + 2 * ( 14 - 2 )  ").value)
);
/*
R2 <- 1
R3 <- 2
R4 <- 3
R5 <- R3 * R4
R3 <- R2 + R5
R2 <- 2
R4 <- 14
R5 <- 2
S0 <- R4 - R5
R4 <- R2 * S0
R2 <- R3 + R4
ret R2
Result = 31
*/

あってますねー。もう一つの例もやってみましょう。

System.println(
    "Result = ",
    new Compiler({ enableListing: true })
        .run(ignore.then(expression)
                   .parseAll("(( 123 ) * 2 * 4 - 3 * ( 12 + 10 )) % 100").value)
);
/*
R2 <- 123
R3 <- 2
R4 <- R2 * R3
R2 <- 4
R3 <- R4 * R2
R2 <- 3
R4 <- 12
R5 <- 10
S0 <- R4 + R5
R4 <- R2 * S0
R2 <- R3 - R4
R3 <- 100
R0 <- R2
R1 <- R3
R4 <- R0 % R1
ret R4
Result = 18
*/

こちらもあってます。

実際のマシン語コードもダンプできるのでやってみましょう。

var code = new Compiler()
       .compile(ignore.then(expression)
                      .parseAll("  1 + 2 * 3 + 2 * ( 14 - 2 )  ").value);
code.dump();
System.println(code.run());

Linux での出力です。

       0:   53                                          push rbx
       1:   41 57                                       push r15
       3:   41 56                                       push r14
       5:   48 8b df                                    mov rbx, rdi
       8:   4c 8b fe                                    mov r15, rsi
       b:   4c 8b f2                                    mov r14, rdx
       e:   48 83 ec 10                                 sub rsp, 0x10
      12:   48 c7 c7 01 00 00 00                        mov rdi, 0x1
      19:   48 c7 c1 02 00 00 00                        mov rcx, 0x2
      20:   49 c7 c0 03 00 00 00                        mov r8, 0x3
      27:   49 89 cb                                    mov r11, rcx
      2a:   4d 0f af d8                                 imul r11, r8
      2e:   4a 8d 0c 1f                                 lea rcx, [rdi+r11]
      32:   48 c7 c7 02 00 00 00                        mov rdi, 0x2
      39:   49 c7 c0 0e 00 00 00                        mov r8, 0xe
      40:   49 c7 c3 02 00 00 00                        mov r11, 0x2
      47:   4c 89 c3                                    mov rbx, r8
      4a:   49 2b db                                    sub rbx, r11
      4d:   49 89 f8                                    mov r8, rdi
      50:   4c 0f af c3                                 imul r8, rbx
      54:   4a 8d 3c 01                                 lea rdi, [rcx+r8]
      58:   48 89 f8                                    mov rax, rdi
      5b:   48 83 c4 10                                 add rsp, 0x10
      5f:   41 5e                                       pop r14
      61:   41 5f                                       pop r15
      63:   5b                                          pop rbx
      64:   c3                                          ret
31

ざっくり読み解いてみましょう。

  • 0xe 行目までがプロローグですね。
  • 0x12 行目で rdi レジスタに 0x01 を代入
  • 0x19 行目で rcx レジスタに 0x02 を代入
  • 0x20 行目で r8 レジスタに 0x03 を代入
  • 0x27-0x2a 行目で r11 レジスタに rcx * r8 の結果を代入
  • 0x2e 行目で rcx レジスタに rdi + r11 の結果を代入
  • 0x32 行目で rdi レジスタに 0x02 を代入
  • 0x39 行目で r8 レジスタに 0x0e を代入
  • 0x40-0x4a 行目で rbx レジスタに r8 - r11 の結果を代入
  • 0x4d-0x50 行目で r8 レジスタに rdi * rbx の結果を代入
  • 0x54 行目で rdi レジスタに rcx + r8 の結果を代入
  • 0x58 行目で rax レジスタに rdi を代入
  • 0x5b 行目からはエピローグです。

関数の復帰値は x64 では rax レジスタと決まっているので、これで結果が返ります。

ではもう一つの例で。

var code = new Compiler()
       .compile(ignore.then(expression)
                      .parseAll("(( 123 ) * 2 * 4 - 3 * ( 12 + 10 )) % 100").value);
code.dump();
System.println(code.run());

こちらも Linux での出力です。

       0:   53                                          push rbx
       1:   41 57                                       push r15
       3:   41 56                                       push r14
       5:   48 8b df                                    mov rbx, rdi
       8:   4c 8b fe                                    mov r15, rsi
       b:   4c 8b f2                                    mov r14, rdx
       e:   48 83 ec 10                                 sub rsp, 0x10
      12:   48 c7 c7 7b 00 00 00                        mov rdi, 0x7b
      19:   48 c7 c1 02 00 00 00                        mov rcx, 0x2
      20:   49 89 f8                                    mov r8, rdi
      23:   4c 0f af c1                                 imul r8, rcx
      27:   48 c7 c7 04 00 00 00                        mov rdi, 0x4
      2e:   4c 89 c1                                    mov rcx, r8
      31:   48 0f af cf                                 imul rcx, rdi
      35:   48 c7 c7 03 00 00 00                        mov rdi, 0x3
      3c:   49 c7 c0 0c 00 00 00                        mov r8, 0xc
      43:   49 c7 c3 0a 00 00 00                        mov r11, 0xa
      4a:   4b 8d 1c 18                                 lea rbx, [r8+r11]
      4e:   49 89 f8                                    mov r8, rdi
      51:   4c 0f af c3                                 imul r8, rbx
      55:   48 89 cf                                    mov rdi, rcx
      58:   49 2b f8                                    sub rdi, r8
      5b:   48 c7 c1 64 00 00 00                        mov rcx, 0x64
      62:   48 89 f8                                    mov rax, rdi
      65:   48 89 ce                                    mov rsi, rcx
      68:   48 99                                       cqo
      6a:   48 f7 fe                                    idiv rsi
      6d:   48 89 d6                                    mov rsi, rdx
      70:   49 89 f0                                    mov r8, rsi
      73:   4c 89 c0                                    mov rax, r8
      76:   48 83 c4 10                                 add rsp, 0x10
      7a:   41 5e                                       pop r14
      7c:   41 5f                                       pop r15
      7e:   5b                                          pop rbx
      7f:   c3                                          ret
18

計算結果もあってますね!

おわりに

通常、スクリプト言語系では AST から独自の中間形式コード(バイトコード)に変換して、それを実行します。Kinx もそうです。ただ、やってることは上記と大差ありません。どちらかというと、Compiler と似たようなことをやって、独自の Interpreter で実行する、といった感じですね。ネイティブなマシン語コードに変換できれば Intrpreter は不要です。普通は多倍長演算とかクラス・オブジェクトとかを扱うし、動的型付け言語は型が実行時まで分からないので、マシン語にコンパイルするのが面倒なだけです。特に、x64(Windows/Linux)/ARM とか色々対応しようとするとそれぞれごとにコンパイラを作らなくてはなりません。なのでバイトコード化がベスト・プラクティスな感じではあります。

そうは言っても本気のプロダクトは JIT 頑張ってますよね。Kinx も native 関数などで一部 JIT 化可能です。しかも抽象化アセンブラ(SLJIT)で表現可能な範囲で実現しているので、基本的には色々なプラットフォームへの移植性は高いはず。

いずれにしても、JIT は言語処理系では昔からホットな話題なので、Kinx の JIT ライブラリはちょっとしたオモチャとしては楽しいかなー、と自画自賛してます。しかも今回頑張ってパーサ・コンビネータなんて実装してしまったので、手軽に独自の DSL とか作ったりできるのではないでしょうか。結構楽しいと思いますがいかがでしょう。

Kinx JIT ライブラリのキャッチフレーズは 「気軽に JIT」、ですね。

ということで、では、また次回。

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

Rubyで配列の中の文字列を数値に変更する方法

array = ["1", "2", "3", "4", "5"]

のように配列の中に文字列で数字の値がある時に、配列の文字列を数値に変更する方法を書きます。

for文の方法

array = ["1", "2", "3", "4", "5"]

intArray = []
for n in array
  intArray.push(n.to_i)
end

# intArray => [1, 2, 3, 4, 5]

arrayとは別の配列intArray = []を作成します。
arrayの値をnで取得して、to_iで文字列を数値に変更、pushintArrayに値を代入していくという処理を繰り返します。

mapメソッドの方法

array = ["1", "2", "3", "4", "5"]

intArray = array.map{ |n| n.to_i }

# intArray => [1, 2, 3, 4, 5]

または

array = ["1", "2", "3", "4", "5"]

intArray = array.map(&:to_i)

# intArray => [1, 2, 3, 4, 5]

mapメソッドで配列arrayを繰り返し処理して値をnという変数に代入して、実行したい処理としてn.to_iまたは&:to_iという処理を実行しています。
実行した処理をintArrayに代入していきます。
こちらの場合は予めintArray = []のように定義しておく必要はありません。

map!を使用する方法もあり、こちらの場合は

array = ["1", "2", "3", "4", "5"]

array.map!{ |n| n.to_i }

# array => [1, 2, 3, 4, 5]

のようにarrayの配列を書き換えることができます。

参考サイト

https://techacademy.jp/magazine/19868
https://uxmilk.jp/21695

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

form_with を使った検索フォームに初期値 value を設定する方法

筆者はRails6.0ですが、Rails5(Rails4も?)でも動くはずです。またSearchkick(Elasticsearchを簡単にするGem)を使っているので、もしかすると環境依存があるかもしれません。

最新のRailsではform_withを使った記法が推奨されています。しかしこと検索フォームの作成においては、未だに情報が少なく苦戦したのでメモ。

正解のコード

# index.html.erb

<%= form_with model: @post, method: "get", local: true do %>
    <%= text_field_tag :search, params[:search], class: "form-control ds-input", placeholder: "検索‥" %>
<% end %>

初期値valueを入れる場合はtext_fieldは使えないようです。form_withを使っているのでURLの指定は不要ですがmethod: "get", local: trueの記述は必要でした。

local: trueというのは form_with のデフォルト設定でAjaxによるHTTPリクエストを送ってしまうのをやめさせているようです(Ajaxだと画面遷移してくれないらしい)。

text_field_tagの引数たちをかっこやハッシュ?({})で囲んでる記述も見かけましたが、よくわからんがこれでいいようです。

post_controller.rb
  def index
    @posts = self.query
  end

  private
    def query
      if params[:search]
        # ここの検索方法の記述はSearchkickを使っているためこうなので、標準の検索方法に置き換えるなどしてください
        Post.search params[:search], fields: [:name, :description, :category_name]
      else
        Post.all
      end
    end

前提知識

筆者はProgateあがりですが、RubyやRailsの公式チュートリアルをやっていないせいかformまわりの基礎知識がバラバラです?もしかすると当たり前かもしれませんが、知らなかった知識を書いときます。

text_field は text_field_tag のヘルパー

フォームの中身に使うtext_fieldtext_field_tagのヘルパーであり、ヘルパーとは簡単にいうと記述を省略して書けるようにしてくれるものらしい?

フォームのコントロールの命名法や大量の属性を扱わなければならず、フォームのマークアップは作成もメンテナンスも退屈な作業になりがちです。そこでRailsでは、フォームのマークアップを生成するビューヘルパーを提供し、こうした煩雑な作業を行わないで済むようにしました
https://railsguides.jp/form_helpers.html

最初の引数はインスタンス変数名、2番目の引数はオブジェクトを呼び出すためのメソッド名

たとえば、コントローラで@personが定義されており、その人物の名前がHenryの場合

<%= text_field(:person, :name) %>
# こうなる?
<input id="person_name" name="person[name]" type="text" value="Henry"/>

なので

<%= text_field :search, params[:search] %>
# と書いてしまうと、こうなってしまう?
<input id="search_" name="search[]" type="text">

Railsの検索に search.html.erb はいらない

検索をする場合は search.html.erb を作成して、ルーティングにGETで追加して、index.html.erb と同じコードを毎回貼り付けてました(笑)コントローラー側でキーワードがあるかないかでif分岐してやればスッキリ書けます。

参考

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

Rails5でECサイトを作る⑤ ~Customerモデル編~

はじめに

架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る④の続きです。
ようやく機能の実装に入ります。まずは顧客サイトの親モデル周辺機能から順番に……ということで、今回はCustomerモデルのCRUDを中心に作ります。

ソースコード

https://github.com/Sn16799/bakeryFUMIZUKI

deviseのコントローラ

deviseで新規登録できず、何でだろうと思ったらコントローラ側にパラメータの許可を与えていなかったためと判明しました。

app/controllers/customers/registrations_controller.rb
class Customers::RegistrationsController < Devise::RegistrationsController

  before_action :authenticate_customer!
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def after_sign_up_path_for(resource)
    customer_top_path
  end

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:is_active, :first_name, :first_name_kana, :family_name, :family_name_kana, :post_code, :address, :tel])
  end

  protected
  def update_resouce(resource, parans)
    resource.update_without_password(params)
  end

end

ついでにsessions_controllerもログイン、ログアウト後の遷移先を指定しておきました。

app/controllers/customers/sessions_controller.rb
class Customers::SessionsController < Devise::SessionsController

  protected

  def after_sign_in_path_for(resource)
    customer_top_path
  end

  def after_sign_out_path_for(resource)
    customer_top_path
  end

end

Bootstrapのflashを使えるようにする

Bootstrapでは、class指定だけできれいに整ったflashメッセージを使うことができます。
部分テンプレートを作っておくとrenderで呼び出せて便利です。

app/controllers/application_controller.rb
add_flash_types :success, :danger, :info
app/helpers/application_helper.rb
def flash_class_for flash_type
  case flash_type
    when 'success' then 'alert-success'
    when 'danger' then 'alert-danger'
    when 'info' then 'alert-info'
  end
end
app/views/layouts/_flash.html.erb
<% flash.each do |type, msg| %>
  <div class="alert <%= flash_class_for(type) %> row" role="alert">
    <a class="close" data-dismiss="alert">×</a>
    <%= msg %>
  </div>
<% end %>
app/views/layouts/application.html.erb
<body>
  <%= render 'layouts/header' %>
  <div class="container-fluid">
    <%= render 'layouts/flash', flash: flash %>
    <div class="row">
      <%= yield %>
    </div>
  </div>
  <%= render 'layouts/footer' %>
</body>

Controller

多くの機能は基本的なCRUDですが、退会処理のみ異なります。

編集画面にて「退会する」ボタンを押下(withdrawアクション呼出)
↓
退会確認画面に遷移
↓
「退会する」ボタンを押下(alert出現)
↓
「はい」を押すと退会処理が行われる(withdraw_doneアクション呼出)

以上のような流れになっています。
また、退会処理はCustomerデータの削除ではなく、is_activeカラムをtrueからfalseに変更する処理としています。

app/controllers/customers_controller.rb
class CustomersController < ApplicationController

  before_action :authenticate_customer!
  before_action :set_customer
  before_action :baria_customer

  def edit
  end

  def show
  end

  def update
    @customer = current_customer
    if @customer.update(customer_params)
      redirect_to customer_path(@customer), success: 'お客様情報が更新されました!'
    else
      flash[:danger] = 'お客様の情報を更新出来ませんでした。空欄の箇所はありませんか?'
      render :edit
    end
  end

  def withdraw
  end

  def withdraw_done
    @customer = current_customer
    @customer.update(is_active: false)
    reset_session
    redirect_to customer_top_path, info: 'ありがとうございました。またのご利用を心よりお待ちしております。'
  end

  private

  def customer_params
    params.require(:customer).permit(:is_active, :first_name, :first_name_kana, :family_name, :family_name_kana, :post_code, :address, :email, :tel, cart_items_attributes: [:_destroy])
  end

  def set_customer
    @customer = current_customer
  end

  # 他者のページにアクセスしようとすると直前のページに戻る
  def baria_customer
    if params[:id].to_i != current_customer.id
      redirect_back(fallback_location: root_path)
    end
  end
end

View

Sign_up

app/views/customers/registrations/new.html.erb
<div class='col-lg-6 offset-lg-3 offset-2 space'>
  <div class='row'>
    <h2>新規会員登録</h2>
  </div>

  <div class='row'>
    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
    <%= render 'devise/shared/error_messages', resource: resource %>


    <div class='form-group row space'>
      <div class='col-lg-4'>
        <h5><%= f.label :名前 %></h5>
      </div>
      <div class='col-lg-8'>
        <div class="row">
          <div class="col-lg-6">
            (姓)<%= f.text_field :family_name, autofocus: true, autocomplete: 'family_name', class: 'form-control' %>
          </div>
          <div class="col-lg-6">
            (名)<%= f.text_field :first_name, autofocus: true, autocomplete: 'first_name', class: 'form-control' %>
          </div>
        </div>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :フリガナ %></h5>
      </div>
      <div class='col-lg-8'>
        <div class="row">
          <div class="col-lg-6">
            (セイ)<%= f.text_field :family_name_kana, autofocus: true, autocomplete: 'family_name_kana', class: 'form-control' %>
          </div>
          <div class="col-lg-6">
            (メイ)<%= f.text_field :first_name_kana, autofocus: true, autocomplete: 'first_name_kana', class: 'form-control' %>
          </div>
        </div>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :Eメール %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :郵便番号(ハイフンなし) %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :post_code, autofocus: true, autocomplete: 'post_code', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :住所 %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :address, autofocus: true, autocomplete: 'address', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :電話番号(ハイフンなし) %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :tel, autofocus: true, autocomplete: 'tel', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :パスワード(6文字以上) %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.password_field :password, autocomplete: 'new-password', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :パスワード確認用 %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form-control' %>
      </div>
    </div>

    <div class='row'>
      <div class='col-lg-2 offset-lg-5 actions'>
        <%= f.submit '新規登録', class:'btn btn-danger' %>
      </div>
    </div>

    <% end %>
  </div>

  <div class="space">
    <h4>すでに登録済みの方</h4>
    <h5><%= link_to 'こちら', new_customer_session_path  %>からログインしてください</h5>
  </div>
</div>

Sign_in

html/app/views/customers/sessions/new.html.erb
<div class='col-lg-6 offset-lg-4 offset-1 space'>
  <div class="row">
    <h3>
      <span style="display: inline-block;">会員の方は</span>
      <span style="display: inline-block;">こちらからログイン</span>
    </h3>
  </div>

  <div class="row">
    <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
    <%= render 'devise/shared/error_messages', resource: resource %>

    <div class="form-group row space">
      <div class="col-lg-4">
        <h5><%= f.label :Eメール %></h5>
      </div>
      <div class="col-lg-8">
        <%= f.text_field :email, autofocus: true, autocomplete: "email", class: 'form-control' %>
      </div>
    </div>

    <div class="form-group row">
      <div class="col-lg-4">
        <h5><%= f.label :パスワード %></h5>
      </div>
      <div class="col-lg-8">
        <%= f.password_field :password, autocomplete: "current-password", class: 'form-control' %>
      </div>
    </div>

    <div>
      <%= link_to "=>パスワードを忘れた方はこちら", new_password_path(resource_name) %>
    </div>

    <div class="form-group row space">
      <div class="col-lg-2 offset-lg-5">
        <%= f.submit "ログイン", class:"btn btn-danger" %>
      </div>
    </div>

    <% end %>
  </div>

  <div class="space">
    <h4>登録がお済みでない方</h4>
    <h5><%= link_to "こちら", new_customer_registration_path  %>から新規登録してください。</h5>
  </div>

</div>

編集

app/views/customers/edit.html.erb
<div class='col-lg-10 offset-lg-1 space'>
  <div class='row'>
    <h2>登録情報編集</h2>
  </div>

  <div class='row'>
    <%= form_with(model: @customer, local: true, class: 'container') do |f| %>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :名前 %></h5>
      </div>
      <div class='col-lg-8'>
        <span style='display: inline-block;'>
          (姓)<%= f.text_field :family_name, autofocus: true, autocomplete: 'family_name', class: 'form-control' %>
        </span>
        <span style='display: inline-block;'>
          (名)<%= f.text_field :first_name, autofocus: true, autocomplete: 'first_name', class: 'form-control' %>
        </span>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :フリガナ %></h5>
      </div>
      <div class='col-lg-8'>
        <span style='display: inline-block;'>
          (セイ)<%= f.text_field :family_name_kana, autofocus: true, autocomplete: 'family_name_kana', class: 'form-control' %>
        </span>
        <span style='display: inline-block;'>
          (メイ)<%= f.text_field :first_name_kana, autofocus: true, autocomplete: 'first_name_kana', class: 'form-control' %>
        </span>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :Eメール %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :郵便番号(ハイフンなし) %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :post_code, autofocus: true, autocomplete: 'post_code', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :住所 %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :address, autofocus: true, autocomplete: 'address', class: 'form-control' %>
      </div>
    </div>

    <div class='form-group row'>
      <div class='col-lg-4'>
        <h5><%= f.label :電話番号(ハイフンなし) %></h5>
      </div>
      <div class='col-lg-8'>
        <%= f.text_field :tel, autofocus: true, autocomplete: 'tel', class: 'form-control' %>
      </div>
    </div>

    <div class="form-group row">
      <div class="col-lg-4 offset-lg-8">
        <%= f.submit '編集内容を保存する', class:'btn btn-danger' %>
        <%= link_to '退会する', customer_withdraw_path(@customer), class:'btn btn-danger'  %>
      </div>
    </div>
    <% end %>
  </div>
</div>

詳細

app/views/customers/show.html.erb
<div class="col-lg-6 offset-lg-3 space">
  <div class="container">
    <h2>マイページ</h2>
  </div>
  <!-- お客様情報 -->
  <div class="container space">
    <div class="row">
      <div class="col-lg-5"><h3>お客様情報</h3></div>
      <div class="col-lg-2">
        <%= link_to '編集', edit_customer_path, class: 'btn-sm btn-danger' %>
      </div>
      <div class="col-lg-4">
        <%= link_to 'パスワード変更', new_customer_password_path, class: 'btn-sm btn-danger' %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>氏名</h4>
      </div>
      <div class="col-lg-9">
        <%= @customer.full_name %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>カナ</h4>
      </div>
      <div class="col-lg-9">
        <%= @customer.family_name_kana %> <%= @customer.first_name_kana %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>郵便番号</h4>
      </div>
      <div class="col-lg-9"><%= @customer.post_code %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>住所</h4>
      </div>
      <div class="col-lg-9">
        <%= @customer.address %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>電話番号</h4>
      </div>
      <div class="col-lg-9">
        <%= @customer.tel %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>Eメール</h4>
      </div>
      <div class="col-lg-9">
        <%= @customer.email %>
      </div>
    </div>
  </div>

  <!-- その他のリンク -->
  <div class="container space">
    <div class="row">
      <div class="col-lg-3">
        <h4>配送先</h4>
      </div>
      <div class="col-lg-9">
        <%= link_to '一覧を見る', addresses_path, class: 'btn btn-danger' %>
      </div>
    </div>

    <div class="row">
      <div class="col-lg-3">
        <h4>注文履歴</h4>
      </div>
      <div class="col-lg-9">
        <%= link_to '一覧を見る', orders_path, class: 'btn btn-danger' %>
      </div>
    </div>
  </div>
</div>

退会確認

app/views/customers/withdraw.html.erb
<div class="col-lg-10 offset-lg-1 space">
  <div class="row space">
    <h2>本当に退会しますか?</h2>
  </div>

  <div class="row space">
    <h4>退会すると、会員登録情報や
      <br>これまでの購入履歴が閲覧できなくなります。
      <br>退会する場合は「退会する」をクリックしてください。
    </h4>
  </div>

  <div class="row space">
    <%= link_to "退会しない",customer_path(current_customer),class: "btn btn-danger" %>
    <%= link_to "退会する", customer_withdraw_done_path(current_customer), method: :put, "data-confirm" => "本当に退会しますか?", class: "btn btn-danger" %>
  </div>
</div>

SCSS

app/assets/stylesheets/application.scss
// initialize
*{
  margin:0;
  padding:0;
  box-sizing:border-box;
  color: #120136;
}

a{
  text-decoration: none;
}

.space {
  padding: 50px 0 30px;
}

後記

今回、機能そのものは難しくないのですが、画面をレスポンシブ対応にしようとするとやはり手間取りました。ボタンは後ほどテーマカラーの色に変更する予定で、今はとりあえず全て赤にしてあります。目立つので。
また、Viewファイルに書いたリンクも一応は機能しますが、飛んだ先のページにまだ何も書いていないので、例の「Find me in ...」という文言が出るだけです。

メインの画面を作ったことで、ようやくサイトらしい見た目になってきましたね。機能はまだまだですが、これから実装していきます。気になっていたIK●Aカラーも、進めるうちに「これはこれでアリなんじゃないか?」と思えるようになってきました。単に見慣れただけかも知れませんが。

写真を載せればきっとパン屋さんのサイトになると信じています。次回へ続く!

参考

RailsでTwitterBootstrapを使ったflashメッセージ

データ登録の際、適当な名前や住所が思い付かない時に便利。「文月太郎」とかでは流石に味気ないので……。
日本人名前自動生成機
ランダム日本地名ジェネレータ

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

ジョブカンスクレイピング

必要なもの

  • selenium-webdriver
  • chromedriver

chromedriver

  1. 下記からChromeのバージョンと同じものをDL http://chromedriver.storage.googleapis.com/index.html
  2. 解凍したらファイルを移動
% sudo mv 解凍したchromedriver /usr/local/bin

3.移動したファイルにパスを通す

% export PATH="/usr/local/bin:$PATH"

selenium-webdriver

gemをインストールする。

%  gem install selenium-webdriver

プログラムについて

リポジトリからgit clone

git clone https://github.com/ryumaanai/jobcan.git

プログラムを編集

1.emailとpasswordを編集する。アスタリスクのところ。
2.出勤の打刻に関しては時間で判別してる。unlessとifのところ。読めばわかると思う。不明点があれば聞いて◎

エイリアスを環境変数に登録しておくと簡単にプログラムを呼び出せる。

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

rubyのエラーの解決策がわかりません...

今日初めてqiitaを使うのでもし使い方間違っていたらすみません.
下のようなエラーが出ていて困っています.

Traceback (most recent call last):
1: from othello.rb:46:in <main>'
othello.rb:14:in
make_bord': undefined local variable or method `max_row' for main:Object (NameError)

色々調べてみたのですが、後ろから順番に実行されてるなど、どう解決したらいいのかわからない文がでてきて困惑しています.わかる方どうかよろしくお願いします.
rubyのverはruby

2.6.6p146 (2020-03-31 revision 67876) [x86_64-darwin19]

です

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

Ruby: Starttls でメールの送信

こちらと同じことを Ruby で行いました。
Python3: Starttls でメールの送信
hi-ho.ne.jp で試しました。

ライブラリーのインストール

sudo gem install mail
hi-ho.rb
#! /usr/bin/ruby
# -*- encoding: utf-8 -*-
#
#   hi-ho.rb
#
#                   Jul/08/2020
#
# ---------------------------------------------------------------------
require "mail"
require "dotenv"

STDERR.puts "*** 開始 ***"

Dotenv.load
server = ENV['SERVER']
port = ENV['PORT']
usr = ENV['USR']
password = ENV['PASSWORD']
from = ENV['FROM']
to = ENV['TO']
#
puts server
puts port

str_out = "Good Morning\n"
str_out += "こんにちは。\n"
str_out += "Jul/08/2020\n"
str_out += "PM 15:58\n"

mail = Mail.new do
  from    from
  to      to
  subject "Hello from Hi-ho PM 15:58"
  content_type 'text/plain; charset=UTF-8'
  body    str_out
end

mail.delivery_method(:smtp,
  address:        server,
  port:           port,
  authentication: :login,
  us
er_name:      usr,
  password:       password
)

mail.deliver

STDERR.puts "*** 終了 ***"
# ---------------------------------------------------------------------
.env
SERVER = 'hi-ho.mose-mail.jp'
PORT = 587
USR = '****@hi-ho.ne.jp'
PASSWORD = '****'
FROM = '****@hi-ho.ne.jp'
TO = 'sample@example.com'

実行結果

$ ./hi-ho.rb 
*** 開始 ***
hi-ho.mose-mail.jp
587
*** 終了 ***

次のバージョンで確認しました。

$ ruby --version
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-gnu]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ハッシュの使い方

これまで配列を多用していたのですが、
ある問題を解いていた時に配列だけでは解決できず、
ハッシュで解決できました。
その学びについてのメモ。

前提

Aさん、 Bさん、 Cさんの書籍購入費ランキングを作成する。
Aさん、 Bさん、 Cさんという名前の 3人のエンジニアがおり、
それぞれ 1000円, 3000円, 2000円 の書籍購入費を消費。
この場合、 Bさんが 1位となり、 Cさんが 2位、そして Aさんが 3位となる。

それぞれのエンジニアの名前と、書籍の購入情報が与えられるので、
この情報から書籍購入費ランキングを作成する。

入力

3    -> エンジニアの人数( number_of_engineers )
A B C  -> エンジニアの名前( name_of_engineers )
4    -> エンジニアたちが購入した本の数( number_of_books )
A 1000 -> 本を購入したエンジニアの名前とその本の金額( name, price )
B 1000
B 2000
C 2000

出力

B
C
A

解き方

number_of_engineers = gets.to_i 
name_of_engineers = gets.to_s.split(" ") 
number_of_books = gets.to_i 
name_n_price = {}
name_of_engineers.each{|name| name_n_price[name] = 0}  #解説1
number_of_books.times do
  name, price = gets.to_s.split(" ")
  name_n_price.each_key{ |i| name_n_price[i] += price.to_i if i == name}  #解説2
end
puts name_n_price.sort_by{ |name, price| -price}.to_h.keys  #解説3

解説

解説1

事前に"name_n_price"で空のハッシュを生成しています。
name_of_engineersは現時点で以下のようになっています。

name_of_engineers = ["A", "B", "C"]

この配列の要素(A, B, C)をeachメソッドで取得し、name_n_priceの中のキーとして加えていきます。
また、この時、name_n_price[name] = 0としているので、各キーには 0というバリューが入ります。
この後、"A 1000"などが入力されていくため、初期値の設定として、上記のようにしています。
上記操作によって、"name_n_price"は以下のようになります。

name_n_price = {"A"=> 0, "B"=> 0, "C"=> 0}

解説2

number_of_books.times do ~ end で "A 1000"など計4回の入力を行わせます。
(今回はnumber_of_books = 4)
name, price = gets.to_s.split(" ")にて以下のような配列を生成させます。

name, price = ["A", "1000"]

続いて、name_n_priceのハッシュ内の各キーに対して、値を追加していきます。
そのため、name_n_price内の各要素をeachメソッドで取得していくのですが、
今回はキーのみの取得を行っていきます。
理由はハッシュのキーに値を追加していく事を理解できていれば、
おさらいしておきます。

irb(main):011:0> h = {} 
=> {}
irb(main):012:0> h[:name] = "suzuki"
=> "suzuki"
irb(main):013:0> p h
{:name=>"suzuki"}
=> {:name=>"suzuki"}

要はキー(今回で言う"name")があれば、値(今回で言う"suzuki")を指定して追加が可能です。

そのため、" name_n_price.each_key{} "とします。
他にはバリューだけ取り出すeach_valueもあります。

続いて、name_n_price = {"A"=> 0, "B"=> 0, "C"=> 0}に対して、
Aさんに該当した場合に値を追加していきたいので、if分で条件分岐を行います。
以下の通りです。

if i == name

ここでの " i "には A, B, Cが入ります。
( each_key でキーを取得しており { |i| ~ }としているため)

最後に入力された金額(price)を加えていきます。

name_n_price[i] += price.to_i

上記を分解して書くと、

name_n_price[i] = name_n_price[i] + price.to_i

代入値で書くと

name_n_price[A] = 0 + 1000

なので、

name_n_price = {"A"=> 0, "B"=> 0, "C"=> 0}

name_n_price = {"A"=> 1000, "B"=> 0, "C"=> 0}

になります。

これをあと三回繰り返すと結果的に

name_n_price = {"A"=> 1000, "B"=> 3000, "C"=> 2000}

になります。

解説3

最後にname_n_priceに対して、金額が大きい人からそのキー(名前)を順番に出力します。

ハッシュ内の金額を大きい順(降順)にしたいので、sort_byを使用します。
sortもありますが、こちらはキーでの並び替えであるため、今回は使えません。

ちなみに、使うとこんな感じになります。

irb(main):017:0> h = {"z" => 1, "f" => 3, "a" => 5}
=> {"z"=>1, "f"=>3, "a"=>5}
irb(main):018:0> h.sort
=> [["a", 5], ["f", 3], ["z", 1]]

キーがアルファベット順で並び替えられています。

以下のようにする事で、値を降順にできます。

name_n_price.sort_by{ |name, price| -price}
irb(main):019:0> name_n_price = {"A"=> 1000, "B"=> 3000, "C"=> 2000}
=> {"A"=>1000, "B"=>3000, "C"=>2000}
irb(main):020:0> name_n_price.sort_by{ |name, price| -price}
=> [["B", 3000], ["C", 2000], ["A", 1000]]

これで並び替えができましたが、ハッシュから配列に要素が格納されています。
これは、sort_byやsortを実行すると、なってしまいます。
なぜかは不明です。

今回の条件ではキーだけを取得したいので、
配列をハッシュに戻して、キーを取得します。

配列をハッシュに変換するには、to_hメソッドを使用します。

name_n_price.sort_by{ |name, price| -price}.to_h
=> {"B"=>3000, "C"=>2000, "A"=>1000}

また、キーだけ欲しいので、keysメソッドを使用します。

name_n_price.sort_by{ |name, price| -price}.to_h.keys
=> ["B", "C", "A"]

putsメソッドでは改行されるので、上記をputsで出力してあげれば、
期待通りの答えになります。

以上です。

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

?Gem::LoadErrorが出て、sqlite3をバージョン指定したが改善されない。

初めまして、Macを使っており、最近ターミナルを初めて触り、Rubyをやり始めた者です。
開発環境を整える際につまづいていることについて質問をさせていただきたいです。

現状のバージョンは以下です。
スクリーンショット 2020-07-08 14.40.22.png

現在、Gemfileに'sqlite3'のみ書かれていたものを、
gem 'sqlite3', '~> 1.3.13'
と書き換えたりしましたが、ローカルホストにアクセスしようとした時に以下の表示が出てしまい、
改善されません。

スクリーンショット 2020-07-08 15.14.12.png

全てが初めてで、恥ずかしくなるくらい初歩的な質問になっていると思います。
ですが、どうしても自分で作っていかなければならない状況でして、回答いただけますと嬉しいです。
どうぞよろしくお願いします。

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

form_withのform.〇〇について

form_withのform.〇〇について

form.withのform.〇〇について、わからないところがあったので、備忘録としてまとめていく。

form.text_field

<%= form.text_field :title %>

幅20文字の入力欄が設定される

form.text_area

<%= form.text_area :content %>

40×20の入力欄が設定される

form.email_filed

<%= form.email.field :email %>

投稿内容に@が含まれていないとメールアドレスとみなされず、エラーになる。
テスト時に注意!

他にもいろいろあるが今回はメールの入力欄にてテスト時にエラーが出たので、備忘録としてまとめた。

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

i18nのロケールをJavaScriptにわたす

多言語対応のアプリを作成中に詰まった

jQueryとAjaxを使って、親カテゴリを選択すると子カテゴリの候補がプルダウンで表示されるようにしました。
ところが、子カテゴリだけ、デフォルト表記のベトナム語でしか翻訳されてませんでした。

例:親カテゴリ => 質問
  子カテゴリ => 1,Ngôn ngữ và văn hóa
         2,Tập huấn kỹ thuật
         3,Ứng dụng và thủ tục

原因

Ajaxで子カテゴリを取得する際、コントローラにロケールを渡してないことが原因でした。

category_pulldown.js
$("#parent").on("change", function () {
  // 選択した親カテゴリからIDを取得
  var id = document.getElementById("parent").value;
// 中略
$.ajax({
  type: 'GET',
  data: { parent_id: id }, // IDをparams[:parent_id]に入れて送信
  url: '/categories/pulldown', // categoriesコントローラにて、受け取ったIDで子カテゴリを取得
  dataType: 'json',
}).done(function (children) { // 取得した子カテゴリを表示させる処理...

一部抜粋ですがロケールの取得は以下の方法でやってました。

application_controller.rb
before_action :set_locale

  private

  def set_locale
    I18n.locale = locale
  end

  def locale
    @locale ||= params[:locale] ||= I18n.default_locale
  end

  def default_url_options(options = {})
    options.merge(locale: locale)
  end

現在のロケールをJava Scriptへわたす

まずは、inputタグを使って現在のロケールを保持させます。
以下のようにtype="hidden"と書くと、ユーザーから見ても画面には表示されません。

hoge.html.erb
<input type="hidden" class="current_locale" value="<%= I18n.locale %>">

次にlocale: $('.current_locale').val()を追加すると
親子カテゴリの翻訳言語が統一されました。

category_pulldown.js
$("#parent").on("change", function () {
  // 選択した親カテゴリからIDを取得
  var id = document.getElementById("parent").value;
// 中略
$.ajax({
  type: 'GET',
  data: { parent_id: id, locale: $('.current_locale').val() }, // IDをparams[:parent_id]に入れて送信
  url: '/categories/pulldown', // categoriesコントローラにて、受け取ったIDで子カテゴリを取得
  dataType: 'json',
}).done(function (children) { // 取得した子カテゴリを表示させる処理...

HTMLはRubyからもJavaScriptからも情報の受け渡しができるので便利ですね:smiley:

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

【Ruby on Rails】Chartkickでカラム別集計の円グラフを作成

はじめに

railsでグラフを挿入したかったのでchartkickを使用しました。
導入自体も容易なので、集計結果の円グラフ化の方法を記載します。

参考にしたページ
-chartkick公式ドキュメント
-Railsでシンプルなグラフを扱うならchart-js-rails よりchartkickを使うべし
-爆速で円グラフを実装する[5分]

環境

ruby 2.5.1
Rails 5.2.4.3

前提と目標

ユーザの意見をまとめたvotesテーブルopinionカラム(integer)から円グラフを作成する。
opinionカラムの値が1の場合は賛成、0の場合は反対で集計しますが、他の値やNULL値が混ざっている状態で作成します。

votesテーブル

id opinion
1 1
2 1
3 0
4 1
5 0
6 2
7 NULL
8 3
9 NULL
10 0

完成図

pie_A.png
※実機では円グラフの各要素にマウスカーソルを当てると要素数が表示されます。

Chartkickの導入

参考ページに記載されていますが改めて記載します。

Gemfile
gem "chartkick"   #追記する
Gemfile
$ bundle install
app/javascript/packs/application.js
//= require chartkick
//= require Chart.bundle  #2行追記する

viewページへの反映

表示させるページはどこでもいいですが今回はpostsコントローラーのindex.html.erbにグラフを表示させたいと思います。

app/views/posts/index.html.erb
<%= pie_chart Vote.group(:opinion).count%>

chart (1).png

これだけでは何を表しているのかよくわからないグラフになっています。。

そこでコントローラーファイルに変数とグラフ用のメソッドを定義します。

app/controllers/posts_controller.rb
  def index
    @opinion = Vote.pluck(:opinion)
    @aggregate = aggregateOpinion(@opinion)
    @sum = sumOpinion(@opinion)
  end

  def aggregateOpinion(array)
    result = [["賛成",0],["反対",0],["どちらでもない",0],["無回答",0]]
    array.each do |i|
      if i == 1
        result[0][1] += 1
      elsif i == 0
        result[1][1] += 1
      elsif i == nil
        result[3][1] += 1
      else 
        result[2][1] += 1
      end
    end
    return result
  end

  def sumOpinion(array)
    result = [["総投票数",0],["有効投票数",0],["無効投票数",0]]
    array.each do |i|
      if i == nil
        result[2][1] += 1
      else 
        result[1][1] += 1
      end
    end
    result[0][1] = array.length 
    return result
  end

解説

pluckメソッドで対象のカラムだけを配列で取得しています。
その後、aggregateOpinionメソッドで円グラフ用のデータに整形しています。

result配列のインデックスがそのまま円グラフの表示順になるのでパラメータが
NULL値のものを最後にしてその次に所謂"その他"(今回の場合は[どちらでもない]という名前)が来るようにしています。

sumOpinionメソッドは直接グラフには関係ありませんが総投票数の情報をグラフに載せることが難しいので別途作成しています。

以上の内容を踏まえ、改めてビューページに反映させたいと思います。

app/views/posts/index.html.erb
<%= pie_chart @aggregate, width: "500px"%>

chart (2).png

これでも問題ないですが最後にレイアウトとsumOpinionメソッドで取得した内容をtable要素で表示させて完成です。

app/views/posts/index.html.erb
<%= pie_chart @aggregate,colors: ["#3333cc","#cc3333","#339966","333"], donut: true , width: "500px"%>
<table border="1" width="500">
  <tr>
    <th>総投票数</th>
    <th>有効投票数</th>
    <th>無効投票数</th>
  </tr>
    <tr>
    <th><%=@sum[0][1]%></th>
    <th><%=@sum[1][1]%></th>
    <th><%=@sum[2][1]%></th>
  </tr>
</table> 

pie_A.png

終わりに

いかがでしたでしょうか。もう少しメソッドの部分に応用を効かせられたなぁと自分でも思いますね。。
もちろんchartkickが対応しているのは円グラフだけではないので是非調べてみてください。
見て頂いてありがとうございました!

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

Stack Overflow Developer Survey 2020の要点整理

 この記事の目的

世界のディベロッパーのトレンドを追う上で、非常に重要な資料であるStack Overflow Developer Surveyの最新版である2020年版の要点を整理すること。自分の理解のためだけでなく、毎年出されているにもかかわらず、日本語での情報共有が少ない本調査の要点を日本語で整理することで、多くの日本人の開発者が世界のトレンドを理解することを期待する。

原文
https://insights.stackoverflow.com/survey/2020

 調査概要

・2020年2月に世界中の65,000人以上のディベロッパーにアンケートを実施(注:回答者はアメリカ、インド、ヨーロッパに集中)。
・コロナウイルスが流行る前に調査が実施されたため、仕事や賃金のデータは現時点での情報と異なる可能性があることに注意。

 キーポイント

(1)最も愛された言語はRust, Typescript, Pythonの順番。Rustは五年連続で一位。
(2)回答者の8割がDevOpsが重要だと考えている。
(3)回答者の9割が、プログラミングで壁に当たった時、Stack Overflowを参照している。

 職種

・複数回答ありで、55%がバックエンド、フルスタックと答え、37%がフロントエンドのエンジニアと回答。
・プログラミング経験年数の層の中で一番高い割合が5年から9年で回答者の30%を占めた。5年以下と答えたのは17%。
・一方で、仕事としてプログラミングした経験は5年以下が最多で回答者の40%を占めた。
・回答者の85%は10代でプログラミングを開始した。

 学歴

・46%が学士号を、23%が修士号を取得。また回答者の6割以上が学部時代にコンピューターサイエンスを専攻していた。

 言語その他のランキング

(1)最もポピュラーな技術
Screenshot from 2020-07-07 23-43-55.png

(2)最もポピュラーなフレームワーク
Screenshot from 2020-07-07 23-48-13.png

(3)最もポピュラーなプラットフォーム
Screenshot from 2020-07-07 23-49-21.png

(4)最も必要とされている技術
Screenshot from 2020-07-07 23-51-06.png

(5)最も必要とされているフレームワーク
Screenshot from 2020-07-07 23-52-06.png

(6)最も必要とされているプラットフォーム
Screenshot from 2020-07-07 23-53-30.png

(7)技術ごとの年収ランキング(米ドル換算)
Screenshot from 2020-07-07 23-57-39.png

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

Rubyで繰り返し処理の中で任意の要素数で処理を差し込む方法

この記事で書くこと :pen_ballpoint:

Rubyで数字を整数かどうか判定する文を書こうとしたら、
すぐに自分の中から出てこなかったので
その内容を学びとしてアウトプットする。

繰り返し処理の中で任意の要素数で処理を差し込む方法について
学びとしてアウトプットする。

(記述する処理と内容が適さないため、修正しました。 :bow:  )

特定の数字の倍数かどうかで処理を分ける :deciduous_tree:

pry(main)> test_num = 1
=> 1
pry(main)> test_num.to_f
=> 1.0
pry(main)> test_num.to_f.to_s.split('.')[1] == '0'
=> true

pry(main)> test_num2 = 1.05
=> 1.05
pry(main)> test_num2.to_f
=> 1.05
pry(main)> test_num2.to_f.to_s.split('.')[1] == '0'
=> false

(0..1000).to_a.each do |num|
  if num.fdiv(100).to_s.split('.')[1] == '0' && num != 0
    p '100の倍数です'
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで数字が整数か分数かどうかを判定するやり方

この記事で書くこと :pen_ballpoint:

Rubyで数字を整数かどうか判定する文を書こうとしたら、
すぐに自分の中から出てこなかったので
その内容を学びとしてアウトプットする。

最初に調べた時に見つかって読ませていただいたQiita記事は以下のものです :bow:

Rubyで整数かどうか判定する(正規表現、integer?、to_i、Integer())

自分なりに考えてみたかったので、今回は別の方法を試しています。 :bow:

判定する :deciduous_tree:

数字のまま、判定する方法はなかなか見つからなかったので、文字列で考えることにしました。

pry(main)> test_num = 1
=> 1
pry(main)> test_num.to_f
=> 1.0
pry(main)> test_num.to_f.to_s.split('.')[1] == '0'
=> true # 整数

pry(main)> test_num2 = 1.05
=> 1.05
pry(main)> test_num2.to_f
=> 1.05
pry(main)> test_num2.to_f.to_s.split('.')[1] == '0'
=> false # 分数
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む