- 投稿日:2021-03-15T23:46:22+09:00
【コードリーディング】delete_allでupdateやdeleteを実行するコードはどこなのか
はじめに
以下モデルを実装し、サンプルデータを詰めて
delete_all
メソッドを実行した際、deleteクエリではなく、updateクエリが実行されました。実装したモデル:
user.rbclass User < ApplicationRecord has_many :microposts endmicropost.rbclass Micropost < ApplicationRecord belongs_to :user endサンプルデータ:
user
、user.microposts
にそれぞれサンプルデータが入っています。> user => #<User id: 1, name: "test_user", email: "test@email.com", created_at: "2021-03-09 13:39:20.444759000 +0000", updated_at: "2021-03-09 13:39:20.444759000 +0000"> > user.microposts Micropost Load (1.0ms) SELECT `microposts`.* FROM `microposts` WHERE `microposts`.`user_id` = 1 /* loading for inspect */ LIMIT 11 => #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 1, content: "test content", user_id: 1, created_at: "2021-03-09 13:42:16.405694000 +0000", updated_at: "2021-03-09 13:42:16.405694000 +0000">]>
user.microposts.delete_all
の実行結果:> user.microposts.delete_all Micropost Update All (3.6ms) UPDATE `microposts` SET `microposts`.`user_id` = NULL WHERE `microposts`.`user_id` = 1 => 1このupdateクエリがどの条件のときにどんなコードで実行されるのか、またdeleteのときはどうなるのか気になったため、調べてみました。
環境
Ruby: 3.0.0
Ruby on Rails: 6.1.3
コード確認日: 2020/03/08delete_all実行時のクラスを把握
user.microposts
のクラスを調べてみると、CollectionProxy
になっていました。> user.microposts.class => Micropost::ActiveRecord_Associations_CollectionProxyそのため
CollectionProxy
クラスでdelete_all
メソッドが実行されているようです。仕様を読む
CollectionProxy
クラスのdelete_all
メソッドの仕様を確認します。
Deletes all the records from the collection according to the strategy specified by the :dependent option. If no :dependent option is given, then it will follow the default strategy.
For has_many :through associations, the default deletion strategy is :delete_all.
For has_many associations, the default deletion strategy is :nullify. This sets the foreign keys to NULL.
dependent
オプションの指定内容によって挙動が決まるようです。もしdependent
オプションがなければ、associationの形式によって挙動が異なるようです。
今回はdependent
オプションがなく、has_many
のassociationだけなので、デフォルトの挙動であるnullify(外部キーがNULLになる)となります。コードを読む
実際のコードとbinding.pryを使用して、コードを読んでいきます。
まずはCollectionProxy
から読んでいきます。collection_proxy.rbdef delete_all(dependent = nil) @association.delete_all(dependent).tap { reset_scope } end
@association
は、User
モデルで関連付けしているhas_many
があるので、HasManyAssociation
となります。binding.pryでの確認結果:
> @association => #<ActiveRecord::Associations::HasManyAssociation:0x00007fa9578df970 ・・・そのため、
HasManyAssociation
クラスのdelete_all
メソッドを呼び出します。
HasManyAssociation
クラス自体ではdelete_all
メソッドが定義されていないので、親クラスのCollectionAssociation
クラスを見ていきます。collection_association.rbdef delete_all(dependent = nil) if dependent && ![:nullify, :delete_all].include?(dependent) raise ArgumentError, "Valid values are :nullify or :delete_all" end dependent = if dependent dependent elsif options[:dependent] == :destroy :delete_all else options[:dependent] end delete_or_nullify_all_records(dependent).tap do reset loaded! end end
引数の
dependent
が存在し、:nullify
や:delete_all
を含んでいなければ例外を返しますが、今回は引数のdependent
を設定していないので次に進みます。次の処理では、
delete_or_nullify_all_record
メソッドに渡すdependent
をセットしています。ここでのoptions[:dependent]
は、以下のようなHogeモデルのdependent
の:delete_all
にあたります。hoge.rbclass Hoge < ApplicationRecord has_many :foos, dependent: :delete_all end今回は
has_many
にdependent
オプションがないので、dependent
はnil
となります。
最後にdelete_or_nullify_all_record
メソッドを呼びだします。has_many_association.rbdef delete_or_nullify_all_records(method) count = delete_count(method, scope) update_counter(-count) count end
scope
が新しく出てきたので、その定義を見てみます。collection_association.rbdef scope scope = super scope.none! if null_scope? scope end
親クラスの
scope
を取得しているので、その定義を見ます。association.rbdef scope if (scope = klass.current_scope) && scope.try(:proxy_association) == self scope.spawn else target_scope.merge!(association_scope) end end
klass
をbinding.pryで確認するとMicropost
になっており、klass.current_scope
を確認するとnil
になっています。binding.pryの実行結果:
> klass => Micropost(id: integer, content: string, user_id: integer, created_at: datetime, updated_at: datetime) > klass.current_scope => nilそのため、
target_scope
メソッドを見ていきます。association.rbdef target_scope AssociationRelation.create(klass, self).merge!(klass.scope_for_association) end
target_scope
ではAssociationRelation
クラスをnewしているので、scope
にはAssociationRelation
クラスのインスタンスがセットされます。collection_association.rbの
scope
メソッドに戻り、null_scope?
メソッドを見ていきます。collection_association.rbdef null_scope? owner.new_record? && !foreign_key_present? end
今回は新規レコードではなく、外部キーを持っているのでfalseが返されます。
binding.pryの実行結果:
> owner => #<User:0x00007ffe81de2fb0 id: 1, ・・・ > owner.new_record? => falseそのため、
scope
はAssociationRelation
クラスのインスタンスのままとなります。ではhas_many_association.rbの
delete_or_nullify_all_records
メソッドに戻り、delete_count
メソッドを見ていきます。has_many_association.rbdef delete_count(method, scope) if method == :delete_all scope.delete_all else scope.update_all(nullified_owner_attributes) end end
ここで
method
が、つまりはdependentが:delete_all
であるかどうかで、deleteかupdateかに分かれるようです。今回
method
はnil
なのでupdateとなるのですが、deleteクエリの実行も確認したいため、それぞれのルートを確認します。updateのルート
scope.update_all(nullified_owner_attributes)
が実行されるので、まずはnullified_owner_attributes
メソッドを見ていきます。foreign_association.rbdef nullified_owner_attributes Hash.new.tap do |attrs| attrs[reflection.foreign_key] = nil attrs[reflection.type] = nil if reflection.type.present? end end
このメソッドで
Micropost
の外部キー(user_id
)をnil
として持つHashを作成します。
nullified_owner_attributesメソッドの確認が終わったので、scope
のupdate_all
メソッドを見ていきます。relation.rbdef update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? if eager_loading? relation = apply_join_dependency return relation.update_all(updates) end stmt = Arel::UpdateManager.new stmt.table(arel.join_sources.empty? ? table : arel.source) stmt.key = table[primary_key] stmt.take(arel.limit) stmt.offset(arel.offset) stmt.order(*arel.orders) stmt.wheres = arel.constraints if updates.is_a?(Hash) if klass.locking_enabled? && !updates.key?(klass.locking_column) && !updates.key?(klass.locking_column.to_sym) attr = table[klass.locking_column] updates[attr.name] = _increment_attribute(attr) end stmt.set _substitute_values(updates) else stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) end @klass.connection.update stmt, "#{@klass} Update All" end
このメソッドでは3つの大きな括りができているようなので、順番に見ていきます。
if eager_loading? relation = apply_join_dependency return relation.update_all(updates) endこの
eager_loading?
メソッドは以下で定義されています。relation.rbdef eager_loading? @should_eager_load ||= eager_load_values.any? || includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) end
eager_loadやincludeを行っていれば、trueとして返されるようです。今回は使用していないのでfalseが返されます。次は
Arel::UpdateManager.new
を使用した処理になります。stmt = Arel::UpdateManager.new stmt.table(arel.join_sources.empty? ? table : arel.source) stmt.key = table[primary_key] stmt.take(arel.limit) stmt.offset(arel.offset) stmt.order(*arel.orders) stmt.wheres = arel.constraints if updates.is_a?(Hash) if klass.locking_enabled? && !updates.key?(klass.locking_column) && !updates.key?(klass.locking_column.to_sym) attr = table[klass.locking_column] updates[attr.name] = _increment_attribute(attr) end stmt.set _substitute_values(updates) else stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name)) endここで、実行するクエリの条件をセットしています。
引数で渡されたupdates
はHashのためif文に入ります。locking_enabled?
は楽観的ロックをしているかどうかの判定ですが、今回は行っていないため、_substitute_values
メソッドの実行結果をセットします。
_substitute_values
メソッドは以下のようになっています。relation.rbdef _substitute_values(values) values.map do |name, value| attr = table[name] unless Arel.arel_node?(value) type = klass.type_for_attribute(attr.name) value = predicate_builder.build_bind_attribute(attr.name, type.cast(value)) end [attr, value] end endこのメソッドでは
value
の中身をチェックして、属性と値をセットとして持たせています。
stmt
に条件をセットし終えたので、update
メソッドを実行します。@klass.connection.update stmt, "#{@klass} Update All"
@klass.connection
をbinding.pryで確認するとMysql2Adapter
になっています。binding.pryの実行結果:
> @klass.connection => #<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0x00007fd889132748 ・・・
Mysql2Adapter
クラス自体にはupdate
メソッドがないため、親クラスをたどるとDatabaseStatements
にあることが分かります。database_statements.rbdef update(arel, name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) exec_update(sql, name, binds) end
このupdateメソッドで、
to_sql_and_binds
メソッドを使用してupdateのSQLを作り、実行となります。deleteのルート
updateの確認が終わったので、次はdeleteのルートを確認します。
has_many_association.rbdef delete_count(method, scope) if method == :delete_all scope.delete_all else scope.update_all(nullified_owner_attributes) end end
scope.delete_all
の実行となるので、delete_all
メソッドを確認します。relation.rbdef delete_all invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| value = @values[method] method == :distinct ? value : value&.any? end if invalid_methods.any? raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end if eager_loading? relation = apply_join_dependency return relation.delete_all end stmt = Arel::DeleteManager.new stmt.from(arel.join_sources.empty? ? table : arel.source) stmt.key = table[primary_key] stmt.take(arel.limit) stmt.offset(arel.offset) stmt.order(*arel.orders) stmt.wheres = arel.constraints affected = @klass.connection.delete(stmt, "#{@klass} Destroy") reset affected end最初の部分が異なりますが、あとはupdateのときとほぼ同じになります。
まずは最初の部分を見ていきます。invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| value = @values[method] method == :distinct ? value : value&.any? end if invalid_methods.any? raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end
NVALID_METHODS_FOR_DELETE_ALL
は、同じクラスで以下のように定義されています。INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]そのため、ここでは
distinct
やgroup
などが含まれていれば、delete_all
メソッドを実行せず例外を投げる処理となります。今回は含まれていないため、スキップします。
その後のeager_loading?
メソッドやstmt
の条件セット処理はupdateとほぼ同じのため、スキップします。最後に
@klass.connection.delete
でdelete
メソッドを実行しています。
@klass.connection
はupdateの時と同じくMysql2Adapter
であり、delete
メソッドはupdate
メソッドの定義と同じDatabaseStatements
にあります。database_statements.rbdef delete(arel, name = nil, binds = []) sql, binds = to_sql_and_binds(arel, binds) exec_delete(sql, name, binds) endこちらも
to_sql_and_binds
メソッドで、deleteのSQLを作り、実行となります。おわりに
コードリーディングを行うことで、ドキュメントだけでなく、どこのコードでdeleteとupdateが分岐しているのか知ることができました。
コードの中身を知っておくと、あのコードが動いているのだなとイメージできて良いと感じました。
この記事が誰かのお役に立てれば幸いです。
- 投稿日:2021-03-15T23:31:13+09:00
Devise 複数のモデルで同時にログイン出来ないようにする方法
はじめに
例としてECサイトなどで、deviseをつかってUserモデルとStoreモデルを作成した場合、デフォルトではそれぞれのモデルで同時にログインや新規登録が可能です。今回はそれを修正したいと思います。
コントローラーを表示する
$ rails generate devise:controllers users rails generate devise:controllers adminsルーティング
config/routes.rbdevise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations'} devise_for :stores, controllers: { sessions: 'stores/sessions', registrations: 'stores/registrations'}これを忘れるとコントローラー内でオーバーライドした内容が反映されないので注意しましょう。
モジュールの作成
./controllers/concerns/accessible.rbmodule Accessible extend ActiveSupport::Concern included do prepend_before_action :check_user end protected def check_user if current_store flash[:notice] = '店舗として既にログインしています。' redirect_to(current_store) and return elsif current_user flash[:notice] = 'ユーザーとして既にログインしています。' redirect_to(root_path) and return end end endcheck_userメソッドではそれぞれのモデルでログインしている場合はリダイレクトする処理を行っています。
これをコールバックに登録するのですが、理由は後述しますがbefore_actionではなくprepend_before_actionに登録しています。モジュールの読み込み
./controllers/users/registration_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController include Accessible skip_before_action :check_user, except: [:new, :create] . . . endここでは、自分のアカウント情報にアクセスして編集する目的と、edit,update,destroyアクションはdeviseのデフォルトの機能によりログインしているアカウントしかアクセス出来ないという理由でchek_userメソッドを呼び出していません。
./controllers/users/sessions_controller.rbclass Stores::SessionsController < Devise::SessionsController include Accessible skip_before_action :check_user, only: :destroy . . . endログアウトする必要があるのでdestroyアクションには適用していません。
これで完成です。before_actionではなくprepend_before_actionにコールバック関数を登録している理由
devise/app/controllers/devise/sessions_controller.rb
より、. . prepend_before_action :require_no_authentication, only: [:new, :create] prepend_before_action :allow_params_authentication!, only: :create . . . # POST /resource/sign_in def create self.resource = warden.authenticate!(auth_options) set_flash_message!(:notice, :signed_in) sign_in(resource_name, resource) yield resource if block_given? respond_with resource, location: after_sign_in_path_for(resource) endcreateアクションにはprepend_before_actionが登録されており、それらのメソッドが実行されると
current_userがtrueを返してしまいます。よってモジュール内で指定したcheck_userメソッドはprepend_before_actionに登録する必要があります。参考
最後に
:require_no_authenticationメソッドや:allow_params_authentication!の中身をちゃんと理解していないので理解したいと思います。
何か間違った点や理解が怪しそうなところがあれば是非ご教授ください!
- 投稿日:2021-03-15T22:51:41+09:00
ruby command not found エラー
最近cloud9でrubyの勉強を始めましたので、分からなかったことなどをアウトプットしていきます。
try.rbputs("hello,world!")$ try.rb bash: try.rb: command not found上記ファイルを実行するとコマンドがないとエラーが出てしまいました。
調べた結果、ファイル名の前にrubyをつけないとコマンドとして認識されないようでした。$ ruby try.rb hello,world!
- 投稿日:2021-03-15T22:42:31+09:00
ridgepoleでfulltext indexを貼る方法
MySQLの全文検索インデックス
MySQL5.6から、
fulltext
インデックスを貼ることができるようになった。(pluginによるparserの変更は5.7から?)ridgepoleでfulltext indexを貼ると発生する問題
create_table :fuga do ... t.text :hoge ... end execute("CREATE FULLTEXT INDEX fk_hoge ON fuga(hoge) WITH PARSER ngram")この書き方だと、冪等性がなく、
2回目以降に実行すると、一度、remote_index :hoge
が行われたあとに、再度、execute
のCREATE FULLTEXT INDEX
が呼ばれてしまう。create_table
句でname
に対するindexを貼っていないための模様この状態だと、マイグレーションを走らせるたびに、重いINDEXを貼る作業が毎回行われるので、回避するべき事象となる。
対処方法
create_table :fuga do ... t.text :hoge t.index :hoge, type: :fulltext, ignore: true # これを追加 ... end execute("CREATE FULLTEXT INDEX fk_hoge ON fuga(hoge) WITH PARSER ngram") do |c| # INDEXが登録済みの場合は再実行しない rows = c.raw_connection.query("SHOW INDEX FROM fuga") rows.none? { _1[2] == 'fk_hoge' } end
ignore
を含んだ、TableDefinitionを追加し、execute
にINDEXの存在を確認するブロックを渡すことで、再実行してもOKになる。
超参考
- 投稿日:2021-03-15T22:14:42+09:00
【Rails】Webpacker::Manifest::MissingEntryErrorの対処法【Docker】
環境
Ruby 2.6.6
Rails 6.1.1
Docker 20.10.2はじめに
Docker環境でRailsアプリを開発中に、サーバーを起動しhttp://localhost:3000/にアクセスすると発生。
Rails6ではwebpackerが標準サポートとなり、webpakerのインストールにはパッケージマネージャーであるyarnが必要。エラーの解決方法
1)まずyarnで必要なライブラリをインストールする
$ bin/yarn2)次にwebpackでJavaScriptのコンパイルをする
$ bin/webpack3)最後にJS配信用のdevサーバーを起動する。(自動でwatch&コンパイルされる)
$ bin/webpack-dev-serverこれをした後に一応docker-compose up -dで再起動し、localhost:3000にアクセスすると表示されました!
2と3はどちらかだけでも良いかも??参考
- 投稿日:2021-03-15T21:45:06+09:00
`*': false can't be coerced into Integer (TypeError)
- 投稿日:2021-03-15T21:40:12+09:00
Railsポートフォリオ作成 #9 ゲストログイン機能
こんにちは
今回はゲストログイン機能の実装を行いました。
(前回記事(#8 機能の拡充))私は、前職(ホテルの料飲部)における、コミュニケーションの課題を解決するアプリを作っています。
ゲストログイン機能の実装
に今回は取り組みました。
ポートフォリオにおいては必須の機能だと思われるこの機能ですが、正直言ってどのように実装するのかあまり見当がつきませんでした。
しかし、簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用) という記事が良記事すぎたおかげで、そんなに難しくありませんでした。
実装方法自体は上記の記事に譲るとして、いくつか勉強になったことを備忘録として残したいと思います。
勉強になったこと
find_or_create_by!
DBから当てはまるものを探し出し、無い場合は作ってくれる
SecureRandom
ランダムな値を作ってくれる
パスワードの生成に利用こういった新しいメソッドなどを利用しながら、新しいロジックに触れるのは楽しいです。
まだまだ自分で複雑なロジックを組み立てることはできませんが、少しずつ線になっていく日を願って点を増やしていきたいと思います。
個人的に意識していること
実際に登録されそうな情報を登録して、実際に使われていたらこんな風だという雰囲気がわかるようにすることを意識しながら、ポートフォリオ作りをしようと思っています。
そのため、今回のゲストユーザー情報もその点を意識して登録しました。
今後も雰囲気が伝わるようなデータを登録していきたいと思います。
これで、スタッフとゲストの人数を管理する機能をつければ、最低限の機能はつけ終わることになります。
その後は、UIの向上をメインに、AWS、dockerに再挑戦していきたいと思います。
- 投稿日:2021-03-15T21:04:34+09:00
【RubyonRails】no such tableのエラー
【RubyonRails】no such tableのエラー
Devise導入時にこんなエラーが、
terminal>rails db:migrate == 20210315111515 AddDeviseToUsers: migrating ================================= -- change_table(:users) rails aborted! StandardError: An error has occurred, this and all later migrations canceled: SQLite3::SQLException: no such table: users: ALTER TABLE "users" ADD "email" varchar DEFAULT '' NOT NULL C:/Users/sample/db/migrate/20210315111515_add_devise_to_users.rb:7:in `block in up' bin/rails:4:in `<main>' Caused by: ActiveRecord::StatementInvalid: SQLite3::SQLException: no such table: users: ALTER TABLE "users" ADD "email" varchar DEFAULT '' NOT NULL C:/Users/sample/db/migrate/20210315111515_add_devise_to_users.rb:7:in `block in up' C:/Users/sample/db/migrate/20210315111515_add_devise_to_users.rb:5:in `up' Caused by: SQLite3::SQLException: no such table: users C:/Users/sample/db/migrate/20210315111515_add_devise_to_users.rb:7:in `block in up' C:/Users/sample/db/migrate/20210315111515_add_devise_to_users.rb:5:in `up' bin/rails:4:in `<main>' Tasks: TOP => db:migrate (See full trace by running task with --trace)usersのテーブルが見つかりませんってことっぽい
でも確認したらsersテーブルあるんだよなぁなのでもう一度
terminal>rails g devise user >rails db:migrateを実行。
治りませんでした。なんでだろうと思い新しくプロジェクトを作成しrails db:migrateをするとそちらではできました
何が違うのかなとfileをみていたらエラーが起こる方は20210315111515_add_devise_to_users.rbclass AddDeviseToUsers < ActiveRecord::Migration[5.2] def self.up change_table :users do |t|と記述してあり
エラーが起こらない方は20210315115722_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t|と記述してあり、change_tableをcreate_tableに直したら実行できました!!!!
このエラーに結構苦戦したため備忘録として残しておきます
- 投稿日:2021-03-15T20:50:01+09:00
【Rails】toggleとtoggle!の使い方
toggleとtoggle!
toggle(:hoge)とtoggle!(:hoge)の大きな違いは「データベースにセーブされるかどうか」と「戻り値」の違いになります。
・toggle:インスタンスに保存されているbooleanの値を反転する(データベースには変更を反映しない)。処理成功時に指定のbooleanを反転させたインスタンス自身をreturnしてくれます。(selfを返す)
・toggle!:インスタンスに保存されているbooleanの値を反転させて、データベースに保存してくれます。処理成功時にtrueをreturnしてくれます。
toggle(:hoge)でデータベースに値を保存したい場合はsaveメソッドを使えば保存できます。
使用例
以下、toggle(:activated)の使用例です。
togglerails c #データベースからデータを取得。インスタンスのactivatedの値がtrueである user = User.first >User id: 1, name: "Example User", email: "example@railstutorial.org" activated: true user.activate >true #userインスタンスのacticatedの値がfalseになる user.toggle(:activated) >User id: 1, name: "Example User", email: "example@railstutorial.org" activated: false #再度データベースから代入しなおしたら、user.acticatedがtrueのまま代入される user = User.first >User id: 1, name: "Example User", email: "example@railstutorial.org" activated: true以下、toggle!(:activated)の使用例です。(update_at等、一部割愛しています)
toggle!rails c #データベースからデータを取得。インスタンスのacticatedの値がtrueである user = User.first >User id: 1, name: "Example User", email: "example@railstutorial.org" activated: true user.activate >true #userインスタンスのacticatedの値がfalseになる user.toggle!(:activated) (0.1ms) begin transaction User Update (4.1ms) UPDATE "users" SET "updated_at" = ?, "activated" = ? WHERE "users"."id" = ? ["activated", 0], ["id", 1] (5.7ms) commit transaction >User id: 1, name: "Example User", email: "example@railstutorial.org" activated: false #再度データベースから代入しなおしたら、user.acticatedがfalseで代入される(つまり、データベースに変更が反映されている) user = User.first >User id: 1, name: "Example User", email: "example@railstutorial.org" activated: false参考
Rails の toggle と toggle! の違い
https://www.eiji56.com/2017/06/toggle/
- 投稿日:2021-03-15T19:44:30+09:00
Ruby on Rails 新規プロジェクト作成〜bootstrapのインストールまで
①railsのバージョン確認
gem list rails②sampleフォルダを新規作成
rails _5.2.4.5_ new sample
- 投稿日:2021-03-15T19:44:30+09:00
Ruby on Rails 新規プロジェクト作成〜Bootstrapのインストールまで
①railsのバージョン確認
gem list rails②sample(名前は任意)フォルダを新規作成
rails _5.2.4.5_ new sample③Bootstrapのインストール
一. Gemfileに以下を追加gem 'bootstrap', '~> 4.1.1' gem 'jquery-rails'二. 追加したものをインストール
bundle install三. cssファイルをscssファイルに変更
mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss四. app/assets/stylesheets/application.scssの記述を全て削除して以下を追記
@import "bootstrap";五. app/assets/javascripts/application.jsに以下を追記
//= require jquery3 //= require popper //= require bootstrap-sprocketsこれでBootstrapが使える。
Bootstrapより先にgemをインストールすべきなのかもしれない。
- 投稿日:2021-03-15T19:27:38+09:00
form.collection_check_boxesを複数使ったフォーム欄の作成
form.collection_check_boxesを複数使ったフォームページを作成しようとしたらかな〜〜り苦戦したので、忘れない様に書いていこうと思います。
実現したいこと
現在問題投稿サイトを作成中です。上記画像は投稿した問題のeditページです。問題にどんなタグをつけるかをタググループ(部位、部位詳細)にそれぞれ属するタグから選んで更新でき、かつ、下記画像の様に更新したタグ(頭頸部、胸部、骨)がshowページでチェックがついた状態で表示されることが目標です。(画像のUIはPFの作成途中なので非常に見苦しいですが、ご容赦ください...)
開発環境
Mac OS Catalina: 10.15.7
ruby: 2.6.6
rails: 6.1.1モデル
今回登場するモデルは以下の3つです。(中間テーブルを除く)
Quiz
Tag
TagGroupそれぞれの関係性は
Quiz 多対多 Tag
Quiz 多対多 TagGroup
TagGroup 一対多 Tagになっています。
何故複数のform.collection_check_boxesを複数使ったフォーム欄の作成が複雑なのか
理由は
・複数のinputタグに対して同じ属性は使えない
・仮想カラムを設定しないとeditページを開いた時にチェックボックスにチェックが入った状態にならない以上二つが挙げられます。
複数のform.collection_check_boxesの実装
まず、ダメな実装方法を示します。以下の様にviewを設定します
_form.html.erb<div class="bg-info"> <label for="body-regions">部位</label> <div id="body-regions"> <%= form.collection_check_boxes :tag_ids, @body_regions, :id, :name %> </div> <label for="body-region-details">部位詳細</label> <div id="body-region-details"> <%= form.collection_check_boxes :tag_ids, @body_region_details, :id, :name %> </div> </div>
form.collection_check_boxes
で設定している第一引数をみて欲しいのですが、どちらのinputタグも:tag_ids
となっています。上述した様に、複数のinputタグに対して同じ属性は使えません。ここが一緒だと更新時のParamaterに値が入らなかったり、ストロングパラメータに弾かれたりします。*
form.collection_check_boxes
の引数などを詳しく知りたい方はこちらの記事が参考になると思います。
http://l-light-note.hatenablog.com/entry/2017/10/16/153717そこで、第一引数のメソッドを自分で作ります。第一引数のメソッドは更新対象のオブジェクトのメソッドであるため、今回だとQuizモデルで設定します。
ただし、ここでも注意点があります。Quizモデルの設定です。またまたダメな例を書きます。
model/quiz.rbhas_many :details_ids, class_name: 'Tag'_form.html.erb<label for="body-regions">部位</label> <div id="body-regions"> <%= form.collection_check_boxes :tag_ids, @body_regions, :id, :name %> </div> <label for="body-region-details">部位詳細</label> <div id="body-region-details"> <%= form.collection_check_boxes :details_ids, @body_region_details, :id, :name %> </div>二つ目のinputタグの第一引数をmodelで設定しました。上記の様に設定すると更新はできる様になりますが、editページを開いた時にチェックボックスにチェックが入った状態になりません。(原因は最後までよくわかりませんでした...)
そこで仮想カラムを作ります。そうするとそのカラム名をメソッドの様に呼び出すことができる様になり、上記の問題も解決できました。以下にその設定を書きます。(仮想カラムの設定については以下の記事を参考にしました。)
http://mainichiaisatu.hatenablog.com/entry/2017/03/26/225336model/quiz.rbattr_writer :body_region_tag_ids, :body_region_detail_tag_ids #タググループ(部位)のタグを取得 def body_region_tag_ids @body_region_tag_ids = self.tags.where(tag_group_id: 1).ids.sort if self.tag_ids.present? end #タググループ(部位詳細)のタグを取得 def body_region_detail_tag_ids @body_region_detail_tag_ids = self.tags.where(tag_group_id: 2).ids.sort if self.tag_ids.present? end_form.html.erb<label for="body-regions">部位</label> <div id="body-regions"> <%= form.collection_check_boxes :body_region_tag_ids, @body_regions, :id, :name %> </div> <label for="body-region-details">部位詳細</label> <div id="body-region-details"> <%= form.collection_check_boxes :body_region_detail_tag_ids, @body_region_details, :id, :name %> </div>controllers/quizzes_controller.rbdef update if @quiz.update(quiz_params) && @quiz.update(tag_ids: tag_params) flash[:success] = '問題を更新しました。' redirect_to @quiz else flash[:danger] = '問題は更新されませんでした' render :edit end end private def tag_params params.require(:quiz).permit(body_region_tag_ids: [], body_region_detail_tag_ids: []) params[:quiz][:body_region_tag_ids] + params[:quiz][:body_region_detail_tag_ids] end(1)まずはモデルから設定します。
attr_writer :body_region_tag_ids, :body_region_detail_tag_ids
で仮想カラム(インスタンスメソッド)を指定します。
その後、それぞれのインスタンスメソッドの処理を書きます。今回はタググループに属するタグのidを配列で取得したかったため、上記の様に書きました。(2)ビューにて第一引数を(1)で設定したインスタンスメソッドに書き換えます。
(3)quizzes#updateにて更新処理を書きます。また、ストロングパラメータにてインスタンスメソッドで取得した複数の配列を一つの配列にまとめて取得できる様にします。この配列を使って更新します。
これで実装完了です。バリデーションなどはまだ書けていませんが、忘れないうちに書かせていただきました。
結語
簡単だと思っていましたが、想像以上にかなーり手こずりました。挫けずPF作成を続けていこうと思います。
- 投稿日:2021-03-15T18:22:48+09:00
【Rails】お気に入り機能を実装しよう!
今回もレシピアプリを例にお気に入り機能を実装していきます。
完成イメージ
お気に入り機能の実装
モデルの作成
お気に入りに必要な情報は以下の通りになります。
・どの投稿をお気に入りしたかの情報(レシピ情報)
・誰がお気に入りしたかの情報(ユーザ情報)この2つの情報を保存するためにlikesテーブルを作成します。
ターミナルrails g model likes上記のコマンドで作成されたマイグレーションファイルを以下のように編集します。
references型でuserとrecipeに紐付けます。db/migrate/2021xxxxxxxxxx_create_likes.rbclass CreateLikes < ActiveRecord::Migration[6.0] def change create_table :likes do |t| t.references :user, foreign_key: true #追記 t.references :recipe, foreign_key: true #追記 t.timestamps end end endターミナルrails db:migrateこれでlikesテーブルが作成されました。
アソシエーションの設定
UserモデルとLikeモデルの関係は
・ユーザーは複数お気に入り登録することができる
・とあるお気に入り(たとえばlikesID=1)にはユーザーが一人しかいない
となるのでapp/models/user.rbclass User < ApplicationRecord has_many :recipes, dependent: :destroy has_many :likes #追記 #以下略app/models/like.rbclass Like < ApplicationRecord belongs_to :user #追記 endRecipeモデルとLikeモデルの関係は
・1つの投稿(レシピ)は複数お気に入りを持つことができる
・とあるお気に入り(たとえばlikesID=1)に紐づく投稿(レシピ)は1つしかない
となるのでapp/models/recipe.rbclass Recipe < ApplicationRecord belongs_to :user has_many :likes, dependent: :destroy #追記 #以下略app/models/like.rbclass Like < ApplicationRecord belongs_to :user belongs_to :recipe #追記 endバリデーションの設定
ユーザーが1つの投稿に対してお気に入り登録できる回数を1回にします。
つまり、user_idとrecipe_idの組み合わせが重複しないように設定します。
uniquenessヘルパーを使って重複していないか検証します。Railsガイドapp/models/like.rbclass Like < ApplicationRecord belongs_to :user belongs_to :recipe validates :user_id, uniqueness: { scope: :recipe_id } #追記 endルーティングの追加
今回、追加するルーティングはお気に入り情報を保存(create)するルーティングと削除(destroy)するルーティングになります。
また、postsに対してlikesは子の関係になるので、ネストさせどの投稿に紐づくかわかるようにします。config/routes.rbRails.application.routes.draw do #中略 #ここから編集 resources :recipes do resources :likes, only: [:create, :destroy] end #ここまで編集 endコントローラーの作成、編集
お気に入り機能に使うコントローラーを作成します。
先ほど、ルーティングでlikesと指定したのでコントローラー名をlikesとします。ターミナルrails g controller likes次にアクションを追加していきます。
親モデルに属する子モデルのインスタンスを新たに生成するのでbuildを使います。app/controllers/likes_controller.rbclass LikesController < ApplicationController before_action :authenticate_user! def create @like = current_user.likes.build(like_params) @recipe = @like.recipe if @like.valid? @like.save redirect_to recipe_path(@recipe) end end def destroy @like = Like.find(params[:id]) @recipe = @like.recipe if @like.destroy redirect_to recipe_path(@recipe) end end private def like_params params.permit(:recipe_id) end endメソッドの作成
ビューファイルを編集する前に現在サインインしているユーザーがお気に入り登録しているかどうか判断するためのメソッドを作成します。
find_byでuser_idとrecipe_idが一致するlikeを探し、なければnilを返します。
app/models/recipe.rbclass Recipe < ApplicationRecord belongs_to :user has_many :likes, dependent: :destroy #中略 #ここから追加 def liked_by(user) Like.find_by(user_id: user.id, recipe_id: id) end #ここまで追加 endビューファイルの編集
current_user != @recipe.userで自分の投稿には表示しないようにし、条件分岐に先ほど作成したliked_byメソッドを使い表示させるボタンを変化させます。
また、link_toメソッドはデフォルトではgetなのでmethod:を指定します。
app/views/recipes/show.html.erb#中略 <div class="recipe-name"> <%= @recipe.title %> </div> <div> 投稿者: <%= @recipe.user.name %>さん </div> <div class="recipe-content"> カテゴリー: <span class="recipe-category"><%= @recipe.category.name %></span> 所要時間: <span class="recipe-time"><%= @recipe.time_required.name %></span> </div> #ここから追加 <% if current_user != @recipe.user %> <div class="recipe-like"> <% if @recipe.liked_by(current_user).blank? %> <%= link_to "お気に入りに保存", recipe_likes_path(@recipe), method: :post, class: "btn btn-success btn-sm" %> <% else %> <%= link_to "保存済み", recipe_like_path(@recipe,@recipe.liked_by(current_user)), method: :delete, class: "btn btn-outline-success btn-sm" %> <% end %> </div> <% end %> #ここまで追加 #以下略以上でお気に入り登録機能が実装できました。
挙動を確認してみましょう。
お気に入りに登録したレシピを表示させよう!
ユーザーマイページにお気に入りに登録したレシピを表示させます。
ルーティングの設定
まずは、お気に入り一覧ページのルーティングの設定をしてます。
config/routes.rbRails.application.routes.draw do #中略 #ここから編集 resources :users do member do get :like end end #ここまで編集 #以下略コントローラーの編集
whereメソッドでlikesテーブルから自分のidが登録されているレコードを取得し、pluckメソッドで取得したレコードからrecipe_idを配列の形で取得します。
app/controllers/users_controller.rbclass UsersController < ApplicationController #中略 #ここから追加 def like @user = User.find(params[:id]) likes = Like.where(user_id: current_user.id).pluck(:recipe_id) @likes = Recipe.find(likes) end #ここまで追加 endビューファイルの作成、編集
まずはビューファイルを作成します。
ターミナルtouch app/views/users/like.html.erbビューファイルを以下のように編集します。
app/views/users/like.html.erb<div class="like-index text-center"> <div class="user-name"> <%= @user.name %>さんのお気に入り </div> <% if @likes.length != 0 %> <% @likes.each do |recipe| %> <div class="recipe-contents d-flex"> <% if recipe.images.present? %> <%= image_tag recipe.images[0], class: "index-img" %> <% else %> <%= image_tag "no_image.png", class: "index-img" %> <% end %> <div class="recipe"> <div class="recipe-title"> <%= link_to recipe.title, recipe_path(recipe) %> </div> <div class="recipe-content"> カテゴリー: <span class="recipe-category"><%= recipe.category.name %></span> 所要時間: <span class="recipe-time"><%= recipe.time_required.name %></span> </div> </div> </div> <% end %> <% else %> お気に入りはまだ登録されていません <% end %> </div>if文でお気に入り登録を1つもしていない場合は「お気に入りはまだ登録されていません」と表示させるようにしています。
それでは実際に表示してみましょう。お気に入り登録していない場合
お気に入り登録している場合
以上で完成です。
- 投稿日:2021-03-15T16:58:03+09:00
appleとAppleは辞書順ではどちらが先か?
この記事ではpython3.7, ruby2.6.6を使用しています。実行環境はそれぞれPyCharm2020.3とAWSです。
(注 2021年3月15日追記)
文字コードはPythonの標準の文字コードであるUTF-8とします。
ちなみに自分はこの記事を執筆して始めて文字コードを意識しました。1. はじめに
プログラミングでは半角と全角、大文字と小文字は別の文字として認識されますよね。そしてこのことは既に知っていると思います。では質問です。appleとAppleは辞書順ではどちらが先にくるでしょうか?
2. 答え
答えはAppleが先です。Pythonを使って確かめてみましょう。
practice1.pys1 = 'apple' s2 = 'Apple' if s1 > s2: print(f'{s1}は{s2}より前') # -> appleはAppleより前3. apple, Apple, APPLEではどうなるか?
appleとAppleは辞書順ではどちらが先にくるかという疑問が解消したところで次の疑問が生じました。「apple, Apple, APPLEではどうなるのか?」先ほどと同じようにif文を用いて調べてみましょう。
practice2.pys1 = 'apple' s2 = 'Apple' s3 = 'APPLE' if s1 < s2 <s3: print(f'{s1}, {s2}, {s3}の順') elif s1 < s3 < s2: print(f'{s1}, {s3}, {s2}の順') elif s2 < s1 < s3: print(f'{s2}, {s1}, {s3}の順') elif s2 < s3 < s1: print(f'{s2}, {s3}, {s1}の順') elif s3 < s1 < s2: print(f'{s3}, {s1}, {s2}の順') elif s3 < s2 < s1: print(f'{s3}, {s2}, {s1}の順') # ->APPLE, Apple, appleの順答えはAPPLE, Apple, appleの順になることが分かりました。
4. 考察
APPLE, Apple, appleの順になることから分かることは次のとおりです。すなわち「同じ文字ならば辞書順では大文字が小文字に優先する」ということです。
ただしコメントでご指摘があったように文字コードによっては小文字が先に来ることもあるそうです。だったら「この記事は一体何だったんだ?」と思われるかもしれません(tなみに書いた本人は一番思っています)。まあUTF-8とASCIIが大文字優先だからギリ耐えたことにしようしたい(耐えてないけど...)。5. コードを読みやすくする
「同じ文字ならば辞書順では大文字が小文字に優先する」という結論は分かったのですが、apple, Apple, APPLEの順番を比較したコードは条件分岐が6通りもあって読みにくいですよね。読みにくさを解消するためにリストを用います。まずリストを作成して次にsort()メソッドを使用して降順に並べ替えます。
practice3.pyapple_list = ['apple', 'Apple', 'APPLE'] apple_list.sort() print(apple_list)6. まとめ
結論:「同じ文字ならば辞書順では大文字が小文字に優先する」
(この後は余談です。興味がある方は読んでいただけるとありがたいです。)参考文献
『実践式はじめてのPython問題集まとめVer2:Python入門』
余談
今回の疑問は『実践式はじめてのPython問題集まとめVer2:Python入門』で問題演習をしていたときに生じました。自分はこの結果が受け入れられずrubyでも試しました。一応そのときのコードを記載しておきます。あと自分がrubyのコードをQiitaに投稿したのはこれが始めてです。
practice.rbs1 = 'apple' s2 = 'Apple' if s1 < s2 puts("#{s1}は#{s2}より前です") elsif s1 == s2 puts("#{s1}は#{s2}は同じです") else puts("#{s2}は#{s1}より前です") end # -> Appleはappleより前です
- 投稿日:2021-03-15T16:15:54+09:00
Ruby と Ruby on Rails の違い
この記事の目的
Railsを学んでいく過程で、純粋なRubyとの間にどのような違いがあるのかを、気づいた時にその都度アウトプットすることで理解を深めていくことを目指します。
メソッド編
・Rubyでは中身が空のまま定義されたメソッドは何も実行しません。しかしRailsでは中身が空のまま定義されたメソッドでも何らかの振る舞いをする場合があります。例えば、「AplicationController」クラスを継承したクラスの中のメソッドで静的ページのビューを出力することのみを目的とするメソッドの場合は、その静的ページのURLへのルーティングがなされ、対応するビューのファイルが作成されてさえいれば特に中身がないアクションでも動作します。
モジュール編
・Rubyでは作成したモジュールを使う際にincludeを使って明示的に読み込む必要がありますが、Railsではその必要はなく、特に何もしなくてもモジュール内で定義したメソッドを使うことができます。
- 投稿日:2021-03-15T16:01:54+09:00
NoMethodError -よく発生するRubyの例外クラス-
はじめに
初学者です。おそらく一番悩まされたエラーなので備忘録としてまとめました。
間違いあればご指摘お願いします!参考記事「NoMethodError・nil:NilClass」の原因
参考文献 「プロを目指す人のためのRuby入門」NoMethodError
存在しないメソッドを呼び出そうとしているときに発生します。
考えられる原因はいくつかあります。
- メソッド名を間違えちゃったパターン
- レシーバ(※1)の型(クラス)が想定していた型じゃないパターン
- レシーバが想定に対してnilになっているパターン
(※1)レシーバについて
対処法
- ControllerとViewでスペルミスが無いか確認する
- Controllerでコードの書き間違いが無いか確認する
- そのページで対象のアクション(method)が定義されているか、インスタンス変数の(流れ)順番を意識して見なおす
対処法を確認したところで、詳細を見ていきます。
メソッド名を間違えちゃったパターン
メソッドがない = 指定しているメソッドの表記が間違えているというパターン
メソッドを定義しているController,Viewを確認すると間違いが見つかったり。
初学者の自分はよく単数形と複数形を間違えていました。
あとインスタンス変数の@を書き忘れていたりしてもNoMethodErrorになります。レシーバのクラスが想定していた型じゃないパターン
文字列でなくシンボル型で呼び出してしまったというのがよくあるパターンです
そんなことで!?ということでもやらかしてしまうのコワイ...(-_-)レシーバが想定に対してnilになっているパターン[nil:NilClass]
定義されていないmethod(indexやcreateなどのアクション)を使用しようとしているからです。
nilとは「何も存在しない」ということを表すオブジェクトのことです。
また、ハズレの時にnilを返す時があります。よくあるパターンだと、定義まえのmethodを使用している可能性です。
- 投稿日:2021-03-15T15:58:35+09:00
gem banken を飼ってみる
gem banken を導入します。
以下のURLを優しくしただけの記事です(下記のほうが詳しい)。https://github.com/kyuden/banken
https://github.com/kyuden/banken/wiki/Tutorial-(japanese)Gemfilegem "banken"app/controllers/application_controller.rbclass ApplicationController < ActionController::Base include Banken protect_from_forgery end$ bundle install $ rails g banken:install
app/loyalties/
が作成されます。
準備OKです。$ rails g banken:loyalty posts create app/loyalties/posts_loyalty.rbさきほど作成された
ApplicationLoyalty
クラスを継承するPostsLoyalty
クラスがapp/loyalties/
配下に作成されましたapp/loyalties/posts_loyalty.rbclass PostsLoyalty < ApplicationLoyalty def update? user.admin? || record.unpublished? end endapp/controllers/posts_controller.rbclass PostsController < ApplicationController # 他の処理は省略 def update authorize! @post if @post.update(post_params) redirect_to @post, notice: 'Post was successfully updated.' else render :edit end end # 他の処理は省略 end更新処理の実行前に
Banken
が提供するauthorize!
メソッドを呼ぶことで以降の処理が実行可能かどうかを判定することができます。authorize!
は以下の処理を実行します。
PostsController
と同名のPostsLoyalty
クラスのインスタンスを作成する
PostsLoyalty
クラスのインスタンスのupdate?
メソッドを実行する
Banken::NotAuthorizedError
のエラーがでたら成功です!
- 投稿日:2021-03-15T15:19:23+09:00
windowsでrails環境構築で参考にした記事まとめ
- 投稿日:2021-03-15T13:56:47+09:00
Strong Parameters
はじめに
Strong Parametersに少しつまずいたので、今後のためにメモ。
Strong Parametersとは、リクエストパラメータを受け取る際に、想定通りのパラメータかをチェックするもの。
params.require(:user).permit(:name, :email)このように使われます。
意味合い
params.require(:user)この部分で、受け取るパラメータを指定し、
.permit(:name, :email)この部分で、パラメータから取り出しを許可するカラムを指定しています。
まとめ
上述の例では、Userモデルから取り出せるのは、名前とメールアドレスということになり、それ以外は取り出せないようにすることで、セキュリティを担保しています。
想定外の属性を、登録・更新させたくない場合に使用します。
- 投稿日:2021-03-15T12:54:40+09:00
【Ruby】ARC114 A問題 解法メモ
AtcoderRegularContest114のA問題の解法メモです。
自分のようなこれからエンジニアになる人間でも納得できる簡単な考え方がひらめいたので残します。
公式の解説のように全探索しました。
50までの素数[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]15コを掛け合わせて作れる整数2**15コのうち、すべてのXiが最小公倍数1にならない値の最小値が答えになります。# ARC114 # A require 'prime' N = gets.to_i X = gets.split.map &:to_i # 50までの数字のうち素数のみをprimeに格納(全部で15コ) prime = [] Prime.each(50){|i| prime<<i} # 上記15コの素数のうち、掛け合わせて作れるすべての値をpprimeに格納 pprime = [] 1.upto(15) do |i| combi = prime.combination(i).to_a combi.each do |j| pprime << j.inject(:*) end end pprime.sort! # pprimeに格納されている整数のうち、Xすべてと最小公倍数が1にならない最小値がanswer ans = 0 pprime.each do |i| if X.all?{|e| e.gcd(i) != 1} ans = i break end end puts ans
- 投稿日:2021-03-15T11:39:42+09:00
Dockerとdocker-composeを使ったRails APIモードの環境構築
Dockerとdocker-composeを使い、Rails6をAPIモードで動かす環境を構築しました。最後に動作確認もしています。
*記事内容は随時更新していく予定です。今回はオプションなどの細かい部分については説明していません。
前提
- Docker & docker-composeをインストール済み
動作環境
- macOS Catalina 10.15.7
- Ruby 2.7.1
- Ruby on Rails 6.0.3
- Docker 20.10.2
- docker-compose 1.27.4
ディレクトリを作成します。
mkdir rails_api_docker cd rails_api_docker
GemfileとDockerfileを作成します。
Gemfilesource 'https://rubygems.org' gem 'rails', '6.0.3'DockerfileFROM ruby:2.7.1-alpine3.11 ENV BUNDLER_VERSION=2.1.4 WORKDIR /usr/src/app COPY Gemfile . RUN apk update && \ apk add --no-cache \ linux-headers \ libxml2-dev \ curl-dev \ make \ gcc \ libc-dev \ g++ \ sqlite-dev \ tzdata && \ gem install bundler && \ bundle install COPY . .
Dockerイメージを作成します。
docker build -t rails_api:6.0.3 .
作成したイメージからコンテナを起動し、Railsアプリケーションを作成します。
docker run --rm -v $(pwd):/usr/src/app -w /usr/src/app rails_api:6.0.3 rails new . --skip-keeps -M -C -S -J -B
docker-composeでコンテナを起動します。
docker-compose up
localhost:3000
にアクセスします。起動画面が表示されれば成功です。
APIの動作確認
起動中のコンテナにログインします。
docker-compose exec api sh
scaffold
を使ってcontrollerとmodelを作成します。rails g scaffold User name:string
テストデータを作成します。
/db/seeds.rb
に以下を追記してください。seeds.rbUser.create name: "Euclid"
DBを初期化し、テストデータを反映させます。
rails db:create rails db:migrate rails db:seed
/app/controllers/users_controller.rb
のindexアクションを以下に変更します。users_controller.rbdef index @users = User.all render json: @users end
localhost:3000/users
にアクセスして以下が表示されれば成功です。
読んでいただきありがとうございました!ご指摘やご意見などありましたらコメントしていただけると嬉しいです?
- 投稿日:2021-03-15T11:27:48+09:00
ruby genba
initialize
ゲッター、セッター
module (mix-in)とクラス継承
map
- 投稿日:2021-03-15T10:37:41+09:00
Ruby初心者の自分がmruby触ってみた(環境構築)
こんにちは、smpeotnです。
今回は、mruby3.0がリリースされたとういことなので、ちょろっと触ってみようかなと思います。mrubyってなんでしょう?
正直、自分も今日まで知らなかった。
朝、mruby3.0リリースされたってよって記事を見て初めてこうゆうものがあるんだと知りました。
- 省メモリ
- 言語仕様が小さい
- 軽量なRuby言語処理系言語
- LuaのようにかんたんにC・C++に組み込むことができる
なるほど。。。。使い方によっては色々できそうだ!
環境構築してみる
DockerでRuby環境をまずは:こうゆうときDockerって便利ですよね
DockerfileFROM ruby:2.5 RUN mkdir /datadocker-compose.ymlversion: '3' services: ruby: build: . tty: true #コンテナを起動しっぱなしにする volumes: - .:/data# 起動 !! % docker-compose up -dhttps://github.com/mruby/mruby/blob/master/doc/guides/compile.md
ここに書かれてる内容を確認して必要なミドルウェアのインストールとかする
- ruby 2.5
- gcc
- ar% docker-compose exec ruby /bin/bashroot@4c1ad477fc54:/# which gcc /usr/bin/gcc root@4c1ad477fc54:/# which ar /usr/bin/ar root@4c1ad477fc54:/# which ruby /usr/local/bin/ruby root@4c1ad477fc54:/# gcc --version gcc (Debian 8.3.0-6) 8.3.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. root@4c1ad477fc54:/# ar --version GNU ar (GNU Binutils for Debian) 2.31.1 Copyright (C) 2018 Free Software Foundation, Inc. This program is free software; you may redistribute it under the terms of the GNU General Public License version 3 or (at your option) any later version. This program has absolutely no warranty. root@4c1ad477fc54:/# ruby --version ruby 2.5.8p224 (2020-03-31 revision 67882) [x86_64-linux]Build Compile.
root@4c1ad477fc54:/data/data/mruby-3.0.0# rake -v ..... root@4c1ad477fc54:/data/data/mruby-3.0.0# ls -al ./bin/ total 23296 drwxr-xr-x 7 root root 224 Mar 12 15:26 . drwxrwxr-x 38 root root 1216 Mar 12 15:25 .. -rwxr-xr-x 1 root root 5990688 Mar 12 15:25 mirb -rwxr-xr-x 1 root root 3773904 Mar 12 15:25 mrbc -rwxr-xr-x 1 root root 5970456 Mar 12 15:26 mruby -rwxr-xr-x 1 root root 1031 Mar 12 15:25 mruby-config -rwxr-xr-x 1 root root 6040224 Mar 12 15:26 mruby-strip root@4c1ad477fc54:/data/data/mruby-3.0.0# ./bin/mruby --version mruby 3.0.0 (2021-03-05) # パス通す root@4c1ad477fc54:~# export PATH=/data/data/mruby-3.0.0/bin:$PATH root@4c1ad477fc54:~# mruby --version mruby 3.0.0 (2021-03-05)これはDockerfileに書けばいいよね...
DockerfileFROM ruby:2.5 RUN mkdir /data RUN apt-get update && apt-get install -y vim RUN apt-get install -y wget RUN mkdir -p /usr/src/mruby WORKDIR /usr/src/mruby RUN wget https://github.com/mruby/mruby/archive/3.0.0.zip -O mruby-3.0.0.zip && \ unzip mruby-3.0.0.zip && \ cd ./mruby-3.0.0 && \ rake -v ENV PATH $PATH:/usr/src/mruby/mruby-3.0.0/bin WORKDIR /usr/src/mruby/mruby-3.0.0Hello World.(https://github.com/mruby/mruby/wiki/Hello-World)
hello.c#include <stdio.h> #include <mruby.h> #include <mruby/compile.h> int main(void) { mrb_state *mrb = mrb_open(); if (!mrb) { /* handle error */ } puts("Executing Ruby code from C!"); mrb_load_string(mrb, "p 'hello world on mruby! this is ruby code.'"); mrb_close(mrb); return 0; }root@ddd2a0d85a35:/usr/src/mruby/mruby-3.0.0# gcc -Iinclude hello.c build/host/lib/libmruby.a -lm -o hello.out root@ddd2a0d85a35:/usr/src/mruby/mruby-3.0.0# ./hello.out Executing Ruby code from C! "hello world on mruby! this is ruby code."できた!!!!!
Docker簡単。ここまでできれば色々検証できるで
- 投稿日:2021-03-15T00:17:27+09:00
【Ruby】クラスとインスタンス
はじめに
クラスとインスタンスに苦手意識があり、復習を兼ねて記事に残しておこうと思い執筆しました。
飲み物を売っているお店を例にして、ターミナルで以下のように出力できるようにしたいと思います。ターミナルいらっしゃいませ! 水は100円です コーラは150円です モンスターは200円ですクラスの定義
まずクラスを定義します。
クラスの定義は以下のように行います。class クラス名 endクラス名をDrinkとします。
大文字から始めるようにします。drink.rb# クラスの定義 class Drink endインスタンスの生成
newメソッドを使ってインスタンスを生成します。
変数名 = クラス名.newこの後に定義するinitializeメソッドに情報を渡せるように引数も記述します。
drink.rb# クラスの定義 class Drink end # インスタンスの生成 water = Drink.new("水", 100) cola = Drink.new("コーラ", 150) monster = Drink.new("モンスター", 200)initializeメソッドの定義
initializeメソッドを定義します。
newメソッドの引数を(name, price)で受け取り、@nameと@priceにそれぞれ代入しています。drink.rb# クラスの定義 class Drink # initializeメソッドの定義 def initialize(name, price) @name = name @price = price end end # インスタンスの生成 water = Drink.new("水", 100) cola = Drink.new("コーラ", 150) monster = Drink.new("モンスター", 200)クラスメソッドの定義
クラスメソッドの定義はクラスに以下のような記述をします。
メソッド名の前にself.を付けます。def self.メソッド名 endputsメソッドを使い、「いらっしゃいませ!」と出力できるようにします。
drink.rb# クラスの定義 class Drink # クラスメソッドの定義 def self.welcome puts "いらっしゃいませ!" end # initializeメソッドの定義 def initialize(name, price) @name = name @price = price end end # インスタンスの生成 water = Drink.new("水", 100) cola = Drink.new("コーラ", 150) monster = Drink.new("モンスター", 200)インスタンスメソッドの定義
インスタンスメソッドの定義はクラスの中に以下のように記述します。
def メソッド名 end各インスタンスに対して「【名前】は【価格】円です」と出力していきたいので、putsメソッドを使用します。
drink.rb# クラスの定義 class Drink # クラスメソッドの定義 def self.welcome puts "いらっしゃいませ!" end # initializeメソッドの定義 def initialize(name, price) @name = name @price = price end # インスタンスメソッドの定義 def introduce puts "#{@name}は#{@price}円です" end end # インスタンスの生成 water = Drink.new("水", 100) cola = Drink.new("コーラ", 150) monster = Drink.new("モンスター", 200)クラスメソッドとインスタンスメソッドの呼び出し
最後に、定義したクラスメソッドとインスタンスメソッドを呼び出します。
書き方は以下の通りです。# クラスメソッドの呼び出し クラス名.メソッド名 # インスタンスメソッドの呼び出し インスタンス名.メソッド名drink.rb# クラスの定義 class Drink # クラスメソッドの定義 def self.welcome puts "いらっしゃいませ!" end # initializeメソッドの定義 def initialize(name, price) @name = name @price = price end # インスタンスメソッドの定義 def introduce puts "#{@name}は#{@price}円です" end end # インスタンスの生成 water = Drink.new("水", 100) cola = Drink.new("コーラ", 150) monster = Drink.new("モンスター", 200) # クラスメソッドの呼び出し Drink.welcome # インスタンスメソッドの呼び出し water.introduce cola.introduce monster.introduce