20210315のRubyに関する記事は24件です。

【コードリーディング】delete_allでupdateやdeleteを実行するコードはどこなのか

はじめに

以下モデルを実装し、サンプルデータを詰めてdelete_allメソッドを実行した際、deleteクエリではなく、updateクエリが実行されました。

実装したモデル:

user.rb
class User < ApplicationRecord
  has_many :microposts
end
micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user 
end

サンプルデータ:
useruser.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/08

delete_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.rb
def 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.rb
def 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.rb
class Hoge < ApplicationRecord
  has_many :foos, dependent: :delete_all
end

今回はhas_manydependentオプションがないので、dependentnilとなります。
最後にdelete_or_nullify_all_recordメソッドを呼びだします。

has_many_association.rb
def delete_or_nullify_all_records(method)
  count = delete_count(method, scope)
  update_counter(-count)
  count
end

scopeが新しく出てきたので、その定義を見てみます。

collection_association.rb
def scope
  scope = super
  scope.none! if null_scope?
  scope
end

親クラスのscopeを取得しているので、その定義を見ます。

association.rb
def 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.rb
def 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.rb
def null_scope?
  owner.new_record? && !foreign_key_present?
end

今回は新規レコードではなく、外部キーを持っているのでfalseが返されます。

binding.pryの実行結果:

> owner
=> #<User:0x00007ffe81de2fb0
 id: 1,
・・・
> owner.new_record?
=> false

そのため、scopeAssociationRelationクラスのインスタンスのままとなります。

ではhas_many_association.rbのdelete_or_nullify_all_recordsメソッドに戻り、delete_countメソッドを見ていきます。

has_many_association.rb
def 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かに分かれるようです。

今回methodnilなのでupdateとなるのですが、deleteクエリの実行も確認したいため、それぞれのルートを確認します。

updateのルート

scope.update_all(nullified_owner_attributes)が実行されるので、まずはnullified_owner_attributesメソッドを見ていきます。

foreign_association.rb
def 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メソッドの確認が終わったので、scopeupdate_allメソッドを見ていきます。

relation.rb
def 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.rb
def 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.rb
def _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.rb
def 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.rb
def 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.rb
def 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]

そのため、ここではdistinctgroupなどが含まれていれば、delete_allメソッドを実行せず例外を投げる処理となります。今回は含まれていないため、スキップします。
その後のeager_loading?メソッドやstmtの条件セット処理はupdateとほぼ同じのため、スキップします。

最後に@klass.connection.deletedeleteメソッドを実行しています。
@klass.connectionはupdateの時と同じくMysql2Adapterであり、deleteメソッドはupdateメソッドの定義と同じDatabaseStatementsにあります。

database_statements.rb
def 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が分岐しているのか知ることができました。
コードの中身を知っておくと、あのコードが動いているのだなとイメージできて良いと感じました。
この記事が誰かのお役に立てれば幸いです。

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

Devise 複数のモデルで同時にログイン出来ないようにする方法

はじめに

例としてECサイトなどで、deviseをつかってUserモデルとStoreモデルを作成した場合、デフォルトではそれぞれのモデルで同時にログインや新規登録が可能です。今回はそれを修正したいと思います。

コントローラーを表示する

$
rails generate devise:controllers users
rails generate devise:controllers admins

ルーティング

config/routes.rb
devise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations'}
devise_for :stores, controllers: { sessions: 'stores/sessions', registrations: 'stores/registrations'}

これを忘れるとコントローラー内でオーバーライドした内容が反映されないので注意しましょう。

モジュールの作成

./controllers/concerns/accessible.rb
module 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
end

check_userメソッドではそれぞれのモデルでログインしている場合はリダイレクトする処理を行っています。
これをコールバックに登録するのですが、理由は後述しますがbefore_actionではなくprepend_before_actionに登録しています。

モジュールの読み込み

./controllers/users/registration_controller.rb
class 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.rb
class 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)
  end

createアクションにはprepend_before_actionが登録されており、それらのメソッドが実行されると
current_userがtrueを返してしまいます。よってモジュール内で指定したcheck_userメソッドはprepend_before_actionに登録する必要があります。

参考

最後に

:require_no_authenticationメソッドや:allow_params_authentication!の中身をちゃんと理解していないので理解したいと思います。
何か間違った点や理解が怪しそうなところがあれば是非ご教授ください!

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

ruby command not found エラー

最近cloud9でrubyの勉強を始めましたので、分からなかったことなどをアウトプットしていきます。

try.rb
puts("hello,world!")
$ try.rb
bash: try.rb: command not found

上記ファイルを実行するとコマンドがないとエラーが出てしまいました。
調べた結果、ファイル名の前にrubyをつけないとコマンドとして認識されないようでした。

$ ruby try.rb
hello,world!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 が行われたあとに、再度、 executeCREATE 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になる。
超参考

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

【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/yarn

2)次にwebpackでJavaScriptのコンパイルをする

$ bin/webpack

3)最後にJS配信用のdevサーバーを起動する。(自動でwatch&コンパイルされる)

$ bin/webpack-dev-server

これをした後に一応docker-compose up -dで再起動し、localhost:3000にアクセスすると表示されました!
2と3はどちらかだけでも良いかも??

参考

【Rails6】Webpacker::Manifest::MissingEntryErrorが出たときの対処法

【Rails】インストール時につまづきがちなエラー集】

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

`*': false can't be coerced into Integer (TypeError)

aとbの積が偶数か奇数か

はまった書き方

a,b = gets.chomp.split(" ").map(&:to_i)
if a*b.odd? 
  puts "Odd"
else
  puts "Even"
end

エラー文

`*': false can't be coerced into Integer (TypeError)

原因

a*b.odd?の判定において数字のaとb.odd?のboolean値を掛け算しようとしていたため。
(a*b)と書くのが正解。

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

Railsポートフォリオ作成 #9 ゲストログイン機能

こんにちは:smiley:
今回はゲストログイン機能の実装を行いました。
(前回記事(#8 機能の拡充))

私は、前職(ホテルの料飲部)における、コミュニケーションの課題を解決するアプリを作っています。

ゲストログイン機能の実装

に今回は取り組みました。

ポートフォリオにおいては必須の機能だと思われるこの機能ですが、正直言ってどのように実装するのかあまり見当がつきませんでした。

しかし、簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用) という記事が良記事すぎたおかげで、そんなに難しくありませんでした。

実装方法自体は上記の記事に譲るとして、いくつか勉強になったことを備忘録として残したいと思います。

勉強になったこと

find_or_create_by!

DBから当てはまるものを探し出し、無い場合は作ってくれる

SecureRandom

ランダムな値を作ってくれる
パスワードの生成に利用

こういった新しいメソッドなどを利用しながら、新しいロジックに触れるのは楽しいです。
まだまだ自分で複雑なロジックを組み立てることはできませんが、少しずつ線になっていく日を願って点を増やしていきたいと思います。

個人的に意識していること

実際に登録されそうな情報を登録して、実際に使われていたらこんな風だという雰囲気がわかるようにすることを意識しながら、ポートフォリオ作りをしようと思っています。

そのため、今回のゲストユーザー情報もその点を意識して登録しました。
今後も雰囲気が伝わるようなデータを登録していきたいと思います。


これで、スタッフとゲストの人数を管理する機能をつければ、最低限の機能はつけ終わることになります。
その後は、UIの向上をメインに、AWS、dockerに再挑戦していきたいと思います。

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

【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.rb
class AddDeviseToUsers < ActiveRecord::Migration[5.2]
  def self.up
    change_table :users do |t|

と記述してあり
エラーが起こらない方は

20210315115722_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|

と記述してあり、change_tableをcreate_tableに直したら実行できました!!!!
このエラーに結構苦戦したため備忘録として残しておきます

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

【Rails】toggleとtoggle!の使い方

toggleとtoggle!

toggle(:hoge)とtoggle!(:hoge)の大きな違いは「データベースにセーブされるかどうか」「戻り値」の違いになります。

toggle:インスタンスに保存されているbooleanの値を反転する(データベースには変更を反映しない)。処理成功時に指定のbooleanを反転させたインスタンス自身をreturnしてくれます。(selfを返す)

toggle!:インスタンスに保存されているbooleanの値を反転させて、データベースに保存してくれます。処理成功時にtrueをreturnしてくれます。

toggle(:hoge)でデータベースに値を保存したい場合はsaveメソッドを使えば保存できます。

使用例

以下、toggle(:activated)の使用例です。

toggle
rails 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/

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

Ruby on Rails 新規プロジェクト作成〜bootstrapのインストールまで

①railsのバージョン確認

gem list rails

②sampleフォルダを新規作成

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

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をインストールすべきなのかもしれない。

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

form.collection_check_boxesを複数使ったフォーム欄の作成

form.collection_check_boxesを複数使ったフォームページを作成しようとしたらかな〜〜り苦戦したので、忘れない様に書いていこうと思います。

実現したいこと

スクリーンショット 2021-03-14 17.48.41.png

現在問題投稿サイトを作成中です。上記画像は投稿した問題のeditページです。問題にどんなタグをつけるかをタググループ(部位、部位詳細)にそれぞれ属するタグから選んで更新でき、かつ、下記画像の様に更新したタグ(頭頸部、胸部、骨)がshowページでチェックがついた状態で表示されることが目標です。(画像のUIはPFの作成途中なので非常に見苦しいですが、ご容赦ください...)

スクリーンショット 2021-03-14 17.57.54.png

開発環境

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.rb
 has_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/225336

model/quiz.rb
attr_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.rb
  def 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作成を続けていこうと思います。

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

【Rails】お気に入り機能を実装しよう!

今回もレシピアプリを例にお気に入り機能を実装していきます。

完成イメージ

a2b9460dbe7757921f96c6dd75b8b6cb.gif

お気に入り機能の実装

モデルの作成

お気に入りに必要な情報は以下の通りになります。
・どの投稿をお気に入りしたかの情報(レシピ情報)
・誰がお気に入りしたかの情報(ユーザ情報)

この2つの情報を保存するためにlikesテーブルを作成します。

ターミナル
rails g model likes

上記のコマンドで作成されたマイグレーションファイルを以下のように編集します。
references型でuserとrecipeに紐付けます。

db/migrate/2021xxxxxxxxxx_create_likes.rb
class 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.rb
class User < ApplicationRecord
  has_many :recipes, dependent: :destroy
  has_many :likes #追記
#以下略
app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user #追記
end

RecipeモデルとLikeモデルの関係は
・1つの投稿(レシピ)は複数お気に入りを持つことができる
・とあるお気に入り(たとえばlikesID=1)に紐づく投稿(レシピ)は1つしかない
となるので

app/models/recipe.rb
class Recipe < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy  #追記
#以下略
app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :recipe  #追記
end

バリデーションの設定

ユーザーが1つの投稿に対してお気に入り登録できる回数を1回にします。
つまり、user_idとrecipe_idの組み合わせが重複しないように設定します。
uniquenessヘルパーを使って重複していないか検証します。Railsガイド

app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :recipe

  validates :user_id, uniqueness: { scope: :recipe_id }  #追記
end

ルーティングの追加

今回、追加するルーティングはお気に入り情報を保存(create)するルーティングと削除(destroy)するルーティングになります。
また、postsに対してlikesは子の関係になるので、ネストさせどの投稿に紐づくかわかるようにします。

config/routes.rb
Rails.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.rb
class 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.rb
class 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 %>
#ここまで追加
#以下略

以上でお気に入り登録機能が実装できました。
挙動を確認してみましょう。
a2b9460dbe7757921f96c6dd75b8b6cb.gif

お気に入りに登録したレシピを表示させよう!

ユーザーマイページにお気に入りに登録したレシピを表示させます。

ルーティングの設定

まずは、お気に入り一覧ページのルーティングの設定をしてます。

config/routes.rb
Rails.application.routes.draw do
#中略
#ここから編集
  resources :users do
    member do
      get :like
    end
  end
#ここまで編集
#以下略

コントローラーの編集

whereメソッドでlikesテーブルから自分のidが登録されているレコードを取得し、pluckメソッドで取得したレコードからrecipe_idを配列の形で取得します。

app/controllers/users_controller.rb
class 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つもしていない場合は「お気に入りはまだ登録されていません」と表示させるようにしています。
それでは実際に表示してみましょう。

お気に入り登録していない場合

2f7a05068e22dd26473b723a27082aef.png

お気に入り登録している場合

a555f7fed86f55f5613c9bab77ec753e.png

以上で完成です。

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

appleとAppleは辞書順ではどちらが先か?

 この記事ではpython3.7, ruby2.6.6を使用しています。実行環境はそれぞれPyCharm2020.3とAWSです。

(注 2021年3月15日追記)
文字コードはPythonの標準の文字コードであるUTF-8とします。
ちなみに自分はこの記事を執筆して始めて文字コードを意識しました。

1. はじめに

 プログラミングでは半角と全角、大文字と小文字は別の文字として認識されますよね。そしてこのことは既に知っていると思います。では質問です。appleとAppleは辞書順ではどちらが先にくるでしょうか?

2. 答え

 答えはAppleが先です。Pythonを使って確かめてみましょう。

practice1.py
s1 = 'apple'
s2 = 'Apple'
if s1 > s2:
    print(f'{s1}{s2}より前')

# -> appleはAppleより前

3. apple, Apple, APPLEではどうなるか?

 appleとAppleは辞書順ではどちらが先にくるかという疑問が解消したところで次の疑問が生じました。「apple, Apple, APPLEではどうなるのか?」先ほどと同じようにif文を用いて調べてみましょう。

practice2.py
s1 = '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.py
apple_list = ['apple', 'Apple', 'APPLE']
apple_list.sort()
print(apple_list)

6. まとめ

結論:「同じ文字ならば辞書順では大文字が小文字に優先する」
(この後は余談です。興味がある方は読んでいただけるとありがたいです。)

参考文献

『実践式はじめてのPython問題集まとめVer2:Python入門』

余談

 今回の疑問は『実践式はじめてのPython問題集まとめVer2:Python入門』で問題演習をしていたときに生じました。自分はこの結果が受け入れられずrubyでも試しました。一応そのときのコードを記載しておきます。あと自分がrubyのコードをQiitaに投稿したのはこれが始めてです。

practice.rb
s1  = 'apple'
s2 = 'Apple'
if s1 < s2
    puts("#{s1}#{s2}より前です")
elsif s1 == s2
    puts("#{s1}#{s2}は同じです")
else
    puts("#{s2}#{s1}より前です")  
end

# -> Appleはappleより前です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby と Ruby on Rails の違い

この記事の目的

Railsを学んでいく過程で、純粋なRubyとの間にどのような違いがあるのかを、気づいた時にその都度アウトプットすることで理解を深めていくことを目指します。

メソッド編

・Rubyでは中身が空のまま定義されたメソッドは何も実行しません。しかしRailsでは中身が空のまま定義されたメソッドでも何らかの振る舞いをする場合があります。例えば、「AplicationController」クラスを継承したクラスの中のメソッドで静的ページのビューを出力することのみを目的とするメソッドの場合は、その静的ページのURLへのルーティングがなされ、対応するビューのファイルが作成されてさえいれば特に中身がないアクションでも動作します。

モジュール編

・Rubyでは作成したモジュールを使う際にincludeを使って明示的に読み込む必要がありますが、Railsではその必要はなく、特に何もしなくてもモジュール内で定義したメソッドを使うことができます。

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

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を使用している可能性です。

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

gem banken を飼ってみる

gem banken を導入します。
以下のURLを優しくしただけの記事です(下記のほうが詳しい)。

https://github.com/kyuden/banken
https://github.com/kyuden/banken/wiki/Tutorial-(japanese)

Gemfile
gem "banken"
app/controllers/application_controller.rb
class 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.rb
class PostsLoyalty < ApplicationLoyalty
  def update?
    user.admin? || record.unpublished?
  end
end

app/controllers/posts_controller.rb
class 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のエラーがでたら成功です!

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

windowsでrails環境構築で参考にした記事まとめ

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

Strong Parameters

はじめに

Strong Parametersに少しつまずいたので、今後のためにメモ。

Strong Parametersとは、リクエストパラメータを受け取る際に、想定通りのパラメータかをチェックするもの。

params.require(:user).permit(:name, :email)

このように使われます。

意味合い

params.require(:user)

この部分で、受け取るパラメータを指定し、

.permit(:name, :email)

この部分で、パラメータから取り出しを許可するカラムを指定しています。

まとめ

上述の例では、Userモデルから取り出せるのは、名前とメールアドレスということになり、それ以外は取り出せないようにすることで、セキュリティを担保しています。

想定外の属性を、登録・更新させたくない場合に使用します。

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

【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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を作成します。

Gemfile
source 'https://rubygems.org'

gem 'rails', '6.0.3'
Dockerfile
FROM 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にアクセスします。起動画面が表示されれば成功です。
スクリーンショット 2021-03-15 11.06.35.png


APIの動作確認

起動中のコンテナにログインします。

docker-compose exec api sh


scaffoldを使ってcontrollerとmodelを作成します。

rails g scaffold User name:string


テストデータを作成します。/db/seeds.rbに以下を追記してください。

seeds.rb
User.create name: "Euclid"


DBを初期化し、テストデータを反映させます。

rails db:create
rails db:migrate
rails db:seed


/app/controllers/users_controller.rbのindexアクションを以下に変更します。

users_controller.rb
def index
  @users = User.all
  render json: @users
end

localhost:3000/usersにアクセスして以下が表示されれば成功です。

スクリーンショット 2021-03-15 11.32.19.png


読んでいただきありがとうございました!ご指摘やご意見などありましたらコメントしていただけると嬉しいです?

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

ruby genba

initialize
ゲッター、セッター
module (mix-in)とクラス継承
map

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

Ruby初心者の自分がmruby触ってみた(環境構築)

こんにちは、smpeotnです。
今回は、mruby3.0がリリースされたとういことなので、ちょろっと触ってみようかなと思います。

mrubyってなんでしょう?

正直、自分も今日まで知らなかった。
朝、mruby3.0リリースされたってよって記事を見て初めてこうゆうものがあるんだと知りました。

  • 省メモリ
  • 言語仕様が小さい
  • 軽量なRuby言語処理系言語
  • LuaのようにかんたんにC・C++に組み込むことができる

なるほど。。。。使い方によっては色々できそうだ!

環境構築してみる

DockerでRuby環境をまずは:こうゆうときDockerって便利ですよね

Dockerfile
FROM ruby:2.5
RUN mkdir /data
docker-compose.yml
version: '3'
services:
  ruby:
    build: .
    tty: true #コンテナを起動しっぱなしにする
    volumes:
      - .:/data
# 起動 !!
% docker-compose up -d

https://github.com/mruby/mruby/blob/master/doc/guides/compile.md
ここに書かれてる内容を確認して必要なミドルウェアのインストールとかする
- ruby 2.5
- gcc
- ar

% docker-compose exec ruby /bin/bash
root@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に書けばいいよね...

Dockerfile
FROM 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.0

Hello 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簡単。ここまでできれば色々検証できるで

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

【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.メソッド名

end

putsメソッドを使い、「いらっしゃいませ!」と出力できるようにします。

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む