20200628のRubyに関する記事は12件です。

間違えやすいpathの見極め方

pathの見極め方

path名を探す時、普段使うのはターミナルでrails routesして、

               Prefix Verb   URI Pattern                                                                              Controller#Action
    new_admin_session GET    /admins/sign_in(.:format)                                                                admins/sessions#new
        admin_session POST   /admins/sign_in(.:format)                                                                admins/sessions#create
destroy_admin_session DELETE /admins/sign_out(.:format)                                                               admins/sessions#destroy

prefixからpath名を探す。

path名がない

しかし、このようにpath名が書かれていない場合がある。

               Prefix Verb   URI Pattern                                                                              
         edit_product GET    /products/:id/edit(.:format)                                                             products#edit
              product GET    /products/:id(.:format)                                                                  products#show
                      PATCH  /products/:id(.:format)                                                                  products#update
                      PUT    /products/:id(.:format)                                                                  products#update
                      DELETE /products/:id(.:format)                                                                  products#destroy
                 cart GET    /carts/:id(.:format)                                                                     carts#show

products#destroy、products#updateのpath名が見つからない。この場合、path名はproduct_pathになる。prefixでpath名が出てくるまで上に遡っていけばいい。

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

Rubyの繰り返し処理で添字を任意の番号から始める方法

はじめに

タイトルのとおりですが、Rubyで配列をeach_with_indexで繰り返す際に添字を任意の番号から始める方法が個人的に便利だったので、やり方を残しておこうと思います。

やり方

each_with_indexeach.with_index(n)にするだけです。
nには開始番号を指定してください。
添字を1から始める場合は以下のように書きます。

コード

juices = ["tea", "cola", "coffee"]
juices.each.with_index(1) do |juice, i|
    puts "#{i}番目:#{juice}"
end

出力結果

1番目:tea
2番目:cola
3番目:coffee

each_with_indexだとi+1などとする必要があるので、今回の方法だと地味に便利ですよね。

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

【devise】rails メモ 基本設定 初期設定

【ゴール】

devise 初期、基本設定まとめ
意外と、ごちゃっとなってしまうので、、、

【メリット】

■ 作業効率UP
■ devise理解度向上

【開発環境】

■ Mac OS catalina
■ Ruby on Rails (5.2.4.2)
■ Virtual Box:6.1
■ Vagrant: 2.2.7

【コマンド】

deviseを初期化

$ rails g devise:install

※エラーが出れば、
$ bundle exec spring stop

modelを作成

$ rails g devise:アプリ名

各viewを作成

$ rails g devise views アプリ名

各controller作成

$ rails g controllers devise:アプリ名

メール承認機能作成の場合

※Confirmableのコメントアウト外す

db/migrate/devise_creat_アプリ名.rb
## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

APIのログイン機能作成の場合

※Trackableのコメントアウト外す

db/migrate/devise_creat_アプリ名.rb
## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

以上

【合わせて読みたい】

■ 【環境変数】 rails 環境変数とは
https://qiita.com/tanaka-yu3/items/7bf03fee906b80367be9

■ 最速 rails mail devise ウェルカムメール送信機能実装 action mailer不要 一番簡単
https://qiita.com/tanaka-yu3/items/2def7760fd67fe73091a

■ 【devise】 管理者 ユーザー ログイン、ログアウト分ける
https://qiita.com/tanaka-yu3/items/046bf30e08e48fbc42ca

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

【Vue Rails】Vue + Railsで"Hello Vue!"表示 

Vue + Railsアプリ作成

◆ Railsアプリ作成

// "-webpack=vue"オプションでVue.js使用可能
$ rails new <アプリケーション名> -webpack=vue

◆ model作成

// カラム名:name データ型:text
$ rails g model sample name:text

◆ migrationファイル編集(Hello.Vue!表示には不要)

db/migrate/20200627045139_create_sample.rb
class CreateSample < ActiveRecord::Migration[6.0]
  def change
    create_table :sample do |t|
      t.text :name, null: false, default: ""
    end
  end
end

◆ マイグレーション

$ rails db:create      //データベース作成
$ rails db:migrate    //マイグレーション実施

◆ controller作成

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end

◆ routes.rb編集

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

◆ index.html.erb編集

app/views/home/index.html.erb
<%= javascript_pack_tag 'hello_vue' %>
<%= stylesheet_pack_tag 'hello_vue' %>

◆ hello.vue.js(デフォルトで設定済)

app/javascript/packs/hello_vue.js
import Vue from 'vue'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const el = document.body.appendChild(document.createElement('hello'))
  const app = new Vue({
    el,
    render: h => h(App)
  })

  console.log(app)
})

◆ app.vue(デフォルトで設定済)

app/javascript/app.vue
<template>
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

備忘録

◆ before_action

  • メソッドを定義して、before_actionにセットする
login_controller.rb
class LoginController < ApplicationController
  before_action :set_answer

  def set_answer
    @sample = "Hello World!"  
  end
end

◆ rescue_from

  • 例外処理。エラー処理を行う画面を設定する
  • app/controller/application_controller.rbに記述する
app/controller/application_controller.rb
class ApplicationController < ActionController::Base

  rescue_from ActiveRecord::RecordNotFound, with: :rescue404

end

遭遇したエラー

◆ エラー内容①

Webpacker::Manifest::MissingEntryError in Home#index

解決策:Webpackインストール

$ yarn
$ bin/yarn
$ webpack
$ webpack

◆ エラー内容②

Error: vue-loader requires @vue/compiler-sfc to be present in the dependency tree.

解決策:vue-loaderダウングレード

$ npm remove vue-loader
$ npm install --save vue-loader@15.9.2
$ yarn add vue-loader@15.9.2

◆ エラー内容③

Sprockets::Rails::Helper::AssetNotFound in Home#index
 <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

解決策:app/views/layouts/application.html.erb編集

app/views/layouts/application.html.erb
<!-- javascript_include_tagの行を削除 -->
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

参考文献

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

【Rails】フリマアプリ商品編集機能について(プレビュー編集・DB更新)

はじめに

草野と申します。
今回の投稿は、プログラミングスクールでチーム開発にて行ったフリーマーケット系ECサイトのクローンアプリ商品編集機能についてです。自分用のメモのため、文章は拙いですが、少しでも初学者の助けになればと考えています。
内容は、表題にもあるとおり、プレビュー編集とDB更新についてです。未熟な点も多いと思います。不備等ありましたらご指摘ください。随時改善して行こうと思います。ちなみに私は、スクール自体は卒業しており、学習した内容の振り返りとして投稿させて頂いております。

完成品

商品編集画面(プレビュー画像部)

Image from Gyazo

商品編集画面(カテゴリー部)

Image from Gyazo

商品編集画面(販売手数料・利益部)

Image from Gyazo

更新成功時の遷移画面

Image from Gyazo

実装手順

1.ルーティング編集

  • update_doneのルート設定(更新成功時の遷移画面)

2.コントローラー編集

  • editメソッド設定
  • updateメソッド設定
    • エラーハンドリング
    • 画像削除
  • update_daneメソッド設定

3.ビュー編集・作成

  • プレビュー画像呼び出し
  • カテゴリー呼び出しの調整
  • 更新成功時の遷移画面

4.JS編集

  • プレビュー画像及びinputタグの生成、削除
  • 販売手数料・利益の表示

1.ルーティング編集

update_doneルートを生成します。
これは、更新成功時の遷移画面を表示するためのルーティングです。

config/routes.rb 
  resources :items do
    resources :comments,  only: [:create, :destroy]
    resources :favorites, only: [:create, :destroy]
    collection do
      get 'get_category_children', defaults: { fomat: 'json'}
      get 'get_category_grandchildren', defaults: { fomat: 'json'}
      get 'search'
      get 'post_done'
      get 'delete_done'
      get 'detail_search'
      get 'update_done' # これを追加
    end
  end

2.コントローラー編集

今回編集したコントローラーの記述は下記の通りです。

app/controller/items_controller.rb 
class ItemsController < ApplicationController
before_action :category_parent_array, only: [:new, :create, :edit]
before_action :set_item, only: [:show, :edit, :update, :destroy]
before_action :show_all_instance, only: [:show, :edit, :destroy]

# 中略

  def edit
    grandchild = @item.category
    child = grandchild.parent
    if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
    else
     @parent_array = []
     @parent_array << @item.category.parent.parent.name
     @parent_array << @item.category.parent.parent.id
    end
     @category_children_array = Category.where(ancestry: child.ancestry)
     @child_array = []
     @child_array << child.name
     @child_array << child.id

     @category_grandchildren_array = Category.where(ancestry: grandchild.ancestry)
     @grandchild_array = []
     @grandchild_array << grandchild.name
     @grandchild_array << grandchild.id

  end

  def update
    if item_params[:images_attributes].nil?
      flash.now[:alert] = '更新できませんでした 【画像を1枚以上入れてください】'
      render :edit
    else
      exit_ids = []
      item_params[:images_attributes].each do |a,b|
        exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i
      end
      ids = Image.where(item_id: params[:id]).map{|image| image.id }
      delete__db = ids - exit_ids
      Image.where(id:delete__db).destroy_all
      @item.touch
      if @item.update(item_params)
        redirect_to  update_done_items_path
      else
        flash.now[:alert] = '更新できませんでした'
        render :edit
      end
    end
  end

  def update_done
    @item_update = Item.order("updated_at DESC").first
  end

# 中略

  private
  def item_params
    params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])
  end

  def category_parent_array
    @category_parent_array = Category.where(ancestry: nil).each do |parent|
    end
  end

  def show_all_instance
    @user = User.find(@item.user_id)
    @images = Image.where(item_id: params[:id])
    @images_first = Image.where(item_id: params[:id]).first
    @category_id = @item.category_id
    @category_parent = Category.find(@category_id).parent.parent
    @category_child = Category.find(@category_id).parent
    @category_grandchild = Category.find(@category_id)
  end
end

まずeditメソッドを設定します。
他のメソッドで使用しているインスタンス変数を利用するのでリファクタリングのため、before_actionから呼び出しがあります。
使用しているインスタンス変数は下記の通りです。
② 親カテゴリーのnameとidが代入された配列
③ categoryモデル内の全ての子カテゴリー
④ 子カテゴリーのnameとidが代入された配列
⑤ categoryモデル内の全ての孫カテゴリー
⑥ 孫カテゴリーのnameとidが代入された配列
⑦ 該当商品情報
⑧ categoryモデル内の全ての親カテゴリー
⑨ 該当商品の画像
⑩ 該当商品のcategory_id(孫の数値)

app/controller/items_controller.rb 
class ItemsController < ApplicationController
before_action :category_parent_array, only: [:new, :create, :edit]
before_action :set_item, only: [:show, :edit, :update, :destroy]
before_action :show_all_instance, only: [:show, :edit, :destroy]

#中略

  def edit
    # ▼ ①ここで該当商品の子・孫カテゴリーを変数へ代入
    grandchild = @item.category
    child = grandchild.parent
    if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
    else
     # ② ▼ 親カテゴリーのnameとidを配列代入
     @parent_array = []
     @parent_array << @item.category.parent.parent.name
     @parent_array << @item.category.parent.parent.id
    end
     # ③ ▼ 子カテゴリーを全てインスタンス変数へ代入
     @category_children_array = Category.where(ancestry: child.ancestry)
     # ④ ▼ 子カテゴリーのnameとidを配列代入
     @child_array = []
     @child_array << child.name # ⑤で生成した変数を元にname・idを取得
     @child_array << child.id
     # ⑤ ▼ 孫カテゴリーを全てインスタンス変数へ代入
     @category_grandchildren_array = Category.where(ancestry: grandchild.ancestry) 
     # ⑥ ▼ 孫カテゴリーのnameとidを配列代入
     @grandchild_array = []
     @grandchild_array << grandchild.name # ⑤で生成した変数を元にname・idを取得
     @grandchild_array << grandchild.id
  end
  end

#中略

  private
  def item_params
    params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])                                # ⑦ 該当の商品情報をインスタンス変数へ代入
  end

  def category_parent_array
    @category_parent_array = Category.where(ancestry: nil)        # ⑧ 親カテゴリーを全てインスタンス変数へ代入
  end

  def show_all_instance
    @user = User.find(@item.user_id)
    @images = Image.where(item_id: params[:id])                   # ⑨ 該当商品の画像をインスタンス変数へ代入
    @images_first = Image.where(item_id: params[:id]).first
    @category_id = @item.category_id                              # ⑩ 該当商品のレコードからカテゴリーidを取得し、インスタンス変数へ代入(この際に取得するidは孫カテゴリーidです。)
    @category_parent = Category.find(@category_id).parent.parent                    
    @category_child = Category.find(@category_id).parent
    @category_grandchild = Category.find(@category_id)
  end

それぞれを分類分けして並べ替えると下記の通りになります。

商品情報をinputタグに初期値として表示させるためのもの
 ⑦ 該当商品情報
商品画像をプレビューに初期値として表示させるためのもの
 ⑨ 該当商品の画像
カテゴリーをinputタグに初期値として表示させるためのもの

  • 親・子・孫のname・idを取得し、ビュー側のcollection_selectで利用する情報
      ⑩ 該当商品のcategory_id(孫の数値)
      ② 親カテゴリーのnameとidが代入された配列
      ④ 子カテゴリーのnameとidが代入された配列
      ⑥ 孫カテゴリーのnameとidが代入された配列

  • 再入力時にビュー側のcollection_selectで利用する情報
      ⑧ categoryモデル内の全ての親カテゴリー
      ③ categoryモデル内の全ての子カテゴリー
      ⑤ categoryモデル内の全ての孫カテゴリー

次にupdateメソッドの設定です。
editと同様に更新したい商品情報については、before_actionにて呼び出しを行っています。

app/controller/items_controller.rb 
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]

# 中略

  def update
    # ①
    if item_params[:images_attributes].nil?
      flash.now[:alert] = '更新できませんでした 【画像を1枚以上入れてください】'
      render :edit
    else
    # ②
      exit_ids = []
      item_params[:images_attributes].each do |a,b|
        exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i
      end
      ids = Image.where(item_id: params[:id]).map{|image| image.id }
    # ③
      delete__db = ids - exit_ids
      Image.where(id:delete__db).destroy_all
    # ④
      @item.touch
      if @item.update(item_params)
        redirect_to  update_done_items_path
      else
        flash.now[:alert] = '更新できませんでした'
        render :edit
      end
    end
  end


# 中略

  private
  def item_params
    params.require(:item).permit(:name, :item_explanation, :category_id, :item_status, :auction_status, :delivery_fee, :shipping_origin, :exhibition_price,:brand_name, :days_until_shipping, images_attributes: [:image, :_destroy, :id]).merge(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:id])
  end
end

まずこちらの記述は、画像が1枚もない時に更新できないようにif文でエラーハンドリングを記述しています。
item_params[:images_attributes].nil?の記述でparams内の画像が空か確かめています。
.nil?メソッドで空の場合はtureとなり、renderで編集画面に戻りflash.now[:alert]でエラーメッセージを表示します。

    if item_params[:images_attributes].nil?
      flash.now[:alert] = '更新できませんでした 【画像を1枚以上入れてください】'
      render :edit
    else

先ほどのif文でfalseとなった場合に②が動きます。
記述している内容は、exit_idsが更新ボタンを押した時点で入力されている画像のid、idsがDB内に保存されている更新前画像のidとなります。
exit_idsという配列を生成し、item_params[:images_attributes]という多次元配列内に含まれるidの値を取り出したいのでeach文でキーと値を順番に展開します。(|a,b| → a キーのこと b 値のこと)
そして多次元配列から値を取り出すために使用するのがdigメソッドです。
item_params[:images_attributes](多次元配列).dig(:"#{a(親キー)}",:id(子キー)).to_i(数値にする)という記述でidを取り出し、配列に代入します。
そしてidsにはDBから更新前の該当するレコードを取得し、mapメソッドにてidを抽出し代入します。

      exit_ids = []
      item_params[:images_attributes].each do |a,b|
        exit_ids << item_params[:images_attributes].dig(:"#{a}",:id).to_i
      end
      ids = Image.where(item_id: params[:id]).map{|image| image.id }

ちなみにbinding.pryを使用してitem_params[:images_attributes]の中身を確認すると下記のように表示されます。画像1枚で更新ボタンをクリックしました。②のexit_idsに代入したい値は、子の配列内にある"322"という値です。

ターミナル(コンソール起動)
[1] pry(#<ItemsController>)> item_params[:images_attributes]
=> <ActionController::Parameters {"0"=><ActionController::Parameters {"id"=>"322"} permitted: true>} permitted: true>

③では、先ほどのexit_idsとidsを比較し、DBから初期値として編集画面に呼び出されていた画像を削除した場合にDB内の該当データを削除します。idsからexit_idsを引くことで削除されているidだけ残すことができます。それをdelete__dbに代入し、それを元にDBからレコードを検索し、destroy_allメソッドを使って削除します。
_allとしているのは複数レコードの場合も削除できるようにするためです。

      delete__db = ids - exit_ids
      Image.where(id:delete__db).destroy_all

④では、商品情報の更新を行っています。if文のエラーハンドリングにより更新できた場合には、update_dineルートを通り更新成功を伝える画面に遷移します。更新でなかった場合には編集画面に戻り、エラーメッセージを表示します。
一番最初の行に記述している@item.touchはitemsテーブルのupdate_atカラム(更新日時)も含めて更新するためのものです。
これを記述する理由は、後ほどご説明します。

      @item.touch
      if @item.update(item_params)
        redirect_to  update_done_items_path
      else
        flash.now[:alert] = '更新できませんでした'
        render :edit
      end

次にupdate_doneメソッドの設定です。
更新成功を伝える画面には更新した商品詳細ページのリンクを設置しています。
先ほどのupdateメソッドの④で@item.touchを記述することによりitemsテーブルのupdate_atカラム(更新日時)を更新しました。orderメソッド、firstメソッドを使い、update_atカラム内を降順に一番目のものを@item_updateに代入します。

app/controller/items_controller.rb 
  def update_done
    @item_update = Item.order("updated_at DESC").first
  end

3.ビュー編集

全ての記述を載せると長くなってしまうのでここでは割愛して記述させて頂きます。

app/views/items/_form_edit.html.haml 
# ▼ 商品画像についての記載
.new__page__header
  = link_to image_tag("logo/logo.png", alt: "logo"), root_path
= form_for @item do |f|
  = render 'layouts/error_messages', model: f.object
  .name__field#1
    .form__label
      .lavel__name
        出品画像
      .lavel__Required
        [必須]
  #image-box-1{class:"#{@images.last.id}"}
# ▼ ① プレビュー画像の表示
    - @images.each do |img|
      .item-image{id:img.id}
        = image_tag(img.image.url,{width:"188",height:"180"})
        .item-image__operetion
          .item-image__operetion--edit__delete__hidden 削除
    %label.img-label{for: "img-file"}
      #image-box__container{class:"item-num-#{@images.length}"}
        #append-js-edit
          = f.fields_for :images do |image|
            .js-file_group{"data-index" => "#{image.index}"}
              = image.file_field :image, type: 'file', value:"#{image.object.id}",style: "",  id:"img-file", class:'js-file-edit',name: "item[images_attributes][#{@item.images.count}][image]", data:{index:""}
        %i.fas.fa-camera

# 中略

# ▼ カテゴリーについての記載
  .append__category
    .category
      =f.collection_select :category_id, @category_children_array, :id, :name, {selected:@child_array}, {class:"serect_field"}
    - if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
      .category__grandchild#children_wrapper
        =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {},{selected:@grandchild_array, id:"child__category",class:"serect_field"}
    - else
      .category__child#children_wrapper
        =f.collection_select :category_id, @category_children_array, :id, :name, {},{selected:@child_array, id:"child__category", class:"serect_field"}
      .category__grandchild#grandchildren_wrapper
        =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {selected:@grandchild_array}, {class:"serect_field"}

# 省略

下記の記述でプレビュー画像を表示しています。
.item-image_operetion--editdelete_hidden 削除 のhiddenという記述がポイントです。js編集の際にご説明します。

    - @images.each do |img|
      .item-image{id:img.id}
        = image_tag(img.image.url,{width:"188",height:"180"})
        .item-image__operetion
          .item-image__operetion--edit__delete__hidden 削除

下記の記述でカテゴリーを表示しています。
if文で孫なしの場合と孫ありの場合で条件分岐させています。
collection_selectタグに中身の内{}を記述していますがこれはidを付与するにあたり、オプションを記述する際の引数の順番の関係で記述しています。

カテゴリー
  .append__category
    .category
      =f.collection_select :category_id, @category_children_array, :id, :name, {selected:@child_array}, {class:"serect_field"}
    - if @category_id == 46 or @category_id == 74 or @category_id == 134 or @category_id == 142 or @category_id == 147 or @category_id == 150 or @category_id == 158
      .category__grandchild#children_wrapper
        =f.collection_select :category_id, @category_grandchildren_array, :id, :name, {},{selected:@grandchild_array, id:"child__category",class:"serect_field"}
    - else
      .category__child#children_wrapper
        =f.collection_select :category_id, @category_children_array, :id, :name, {},{selected:@child_array, id:"child__category", class:"serect_field"}
      .category__grandchild#grandchildren_wrapper
        =f. :category_id, @category_grandchildren_array, :id, :name, {selected:@grandchild_array}, {class:"serect_field"}

下記の記述は、更新成功時遷移画面のビューファイルになります。

app/views/items/_form_edit.html.haml 
= render "top/header"
.done#fullsize
  .done__title
    商品情報を更新しました
  .done__backlink
    = link_to 'トップページへ戻る', root_path, class: 'link'
  .done__backlink
    = link_to '更新した商品を確認する', item_path(@item_update), class: 'link'
  .done__backlink
    = link_to '出品中の商品一覧を見る', users_path, class: 'link'
= render "top/lower-photo"
= render "top/footer"
= render "top/btn"

4.JS編集

下記の通りjsファイルを編集します。

app/assets/javascript/edit_items.js 
$(function(){
  var dataBox = new DataTransfer();
  var file_field = document.getElementById('img-file')
  $('#append-js-edit').on('change','#img-file',function(){
    $.each(this.files, function(i, file){
      //FileReaderのreadAsDataURLで指定したFileオブジェクトを読み込む
      var fileReader = new FileReader();
      //DataTransferオブジェクトに対して、fileを追加
      dataBox.items.add(file)
      var num = $('.item-image').length + 1 + i
      var aaa = $('.item-image').length + i
// ①
      var image_id = Number($('#image-box-1').attr('class'))
      var append_div_count = Number($('div[id=1]').length) 
      var noreset_id = image_id + append_div_count

      fileReader.readAsDataURL(file);
     //画像が10枚になったら超えたらボックスを削除する
      if (num == 10){
        $('#image-box__container').css('display', 'none')
      }
      //読み込みが完了すると、srcにfileのURLを格納
      fileReader.onloadend = function() {
        var src = fileReader.result
// ②
        var html= `<div class='item-image' data-image="${file.name}" data-index="${aaa}" id="${noreset_id-1}">
                    <div class=' item-image__content'>
                      <div class='item-image__content--icon'>
                        <img src=${src} width="188" height="180" >
                      </div>
                    </div>
                    <div class='item-image__operetion'>
                      <div class='item-image__operetion--edit__delete__file'>削除</div>
                    </div>
                  </div>`
        const buildFileField1 = (num)=> {
// ③
          const html = `<div  class="js-file_group" data-index="${num}" id=1>
                          <input class="js-file-edit" type="file"
                          name="item[images_attributes][${append_div_count+9}][image]"
                          id="img-file" data-index="${num}value="${noreset_id}" >
                        </div>`;
          return html;
        }
        $('.js-file-edit').removeAttr('id');
        //image_box__container要素の前にhtmlを差し込む
        $('.img-label').before(html);
        $('#append-js-edit').append(buildFileField1(num));
      };
      //image-box__containerのクラスを変更し、CSSでドロップボックスの大きさを変えてやる。
      $('#image-box__container').attr('class', `item-num-${num}`)
    });
  });
// ④
  // 10枚登録されていた場合にボックスを消す
  $(document).ready(function(){
    var image_num = $('.item-image').length
    if (image_num==10){
      $('#image-box__container').css('display', 'none')
    }
  });
// ⑤
  $(document).ready(function(){
    $('.js-file-edit').removeAttr('id');
    var num = $('.item-image').length - 1
    var image_id = Number($('#image-box-1').attr('class'))
    var append_div_count = Number($('div[id=1]').length) 
    var noreset_id = image_id + append_div_count
    const buildFileField = (num)=> {
      const html = `<div  class="js-file_group" data-index="${num}" id=1>
                      <input class="js-file-edit" type="file"
                      name="item[images_attributes][100][image]"
                      id="img-file" data-index="${num}" value="${noreset_id}" >
                    </div>`;
      return html;
    }
    $('#append-js-edit').append(buildFileField(num));
  });
// ⑥
  $(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    //削除を押されたプレビューimageのfile名を取得
    var target_id = $(target_image).attr('id');
    var target_image_file = $('input[value="'+target_id+'"][type=hidden]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })
// ⑦
  $(document).on("click", '.item-image__operetion--edit__delete__file', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    var target_id = Number($(target_image).attr('id'));
    //削除を押されたプレビューimageのfile名を取得
    var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })

1行目の記述で一番最後に保存された画像idを取得しimage_id変数に代入します。2行目でビューファイル内のdivでid=1と付与されているタグの数を数えappend_div_count変数に代入します。3行目でそれを足し合わせ、
noreset_id変数に代入します。noreset_idは、画像を追加した際に新たに表示されるinputタグのvalueオプションにセットするためのものです。これを利用して削除の動作を行います。また、プレビュー画像の親となるdivタグにも同じ数値のものを狙って削除するため、idオプションにセットします。(②)

      var image_id = Number($('#image-box-1').attr('class'))
      var append_div_count = Number($('div[id=1]').length) 
      var noreset_id = image_id + append_div_count

下記の記述は、画像データをinputタグに入力した際にイベント発火して生成されるプレビュー画像のHTMLです。

削除の記述でfileという記述がポイントです。ビュー編集時に言っていたhiddenの記述と見分け、新た生成されたinputタグなのか、最初から表示されているinputタグなのか判断します。
 var html= `<div class='item-image' data-image="${file.name}" data-index="${aaa}" id="${noreset_id-1}">
                    <div class=' item-image__content'>
                      <div class='item-image__content--icon'>
                        <img src=${src} width="188" height="180" >
                      </div>
                    </div>
                    <div class='item-image__operetion'>
                      <div class='item-image__operetion--edit__delete__file'>削除</div>
                    </div>
                  </div>`

下記の記述は、画像データをinputタグに入力した際にイベント発火して生成されるinputタグのHTMLです。

 const html = `<div  class="js-file_group" data-index="${num}" id=1>
                          <input class="js-file-edit" type="file"
                          name="item[images_attributes][${append_div_count+9}][image]"
                          id="img-file" data-index="${num}value="${noreset_id}" >
                        </div>`;

下記の記述は、readyメソッドにより画面がロード完了するとイベント発火し、プレビュー画像の数を数えて10枚だった場合に画像を入力するボックスを削除するという記述になります。

  // 10枚登録されていた場合にボックスを消す
  $(document).ready(function(){
    var image_num = $('.item-image').length
    if (image_num==10){
      $('#image-box__container').css('display', 'none')
    }
  });

下記の記述は、readyメソッドにより画面がロード完了するとイベント発火し、inputタグが生成される記述になります。これを行わない場合、最初のinputタグへの入力が、既存の表示されているinputタグに入力されてしまいズレが生じてしまうため、画面ロード時に生成する必要があります。

  $(document).ready(function(){
    $('.js-file-edit').removeAttr('id');
    var num = $('.item-image').length - 1
    var image_id = Number($('#image-box-1').attr('class'))
    var append_div_count = Number($('div[id=1]').length) 
    var noreset_id = image_id + append_div_count
    const buildFileField = (num)=> {
      const html = `<div  class="js-file_group" data-index="${num}" id=1>
                      <input class="js-file-edit" type="file"
                      name="item[images_attributes][100][image]"
                      id="img-file" data-index="${num}" value="${noreset_id}" >
                    </div>`;
      return html;
    }
    $('#append-js-edit').append(buildFileField(num));
  });

下記の記述は、editで呼び出した画像データが入力されているinputタグとプレビュー画像をプレビュー画像の左下に表示されている削除をクリックした際に削除するものです。

  $(document).on("click", '.item-image__operetion--edit__delete__hidden', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    //削除を押されたプレビューimageのfile名を取得
    var target_id = $(target_image).attr('id');
    var target_image_file = $('input[value="'+target_id+'"][type=hidden]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })

下記の記述は、新たに画像がinputタグに入力された際に生成呼び出した画像データが入力されているinputタグと新たに生成されたプレビュー画像をプレビュー画像の左下に表示されている削除をクリックした際に削除するものです。

  $(document).on("click", '.item-image__operetion--edit__delete__file', function(){
    //削除を押されたプレビュー要素を取得
    var target_image = $(this).parent().parent();
    var target_id = Number($(target_image).attr('id'));
    //削除を押されたプレビューimageのfile名を取得
    var target_image_file = $('#append-js-edit').children('div').children('input[value="'+target_id+'"][type=file]');
    //プレビューを削除
    target_image.remove()
    target_image_file.remove()
    //image-box__containerクラスをもつdivタグのクラスを削除のたびに変更
    var num = $('.item-image').length
    $('#image-box__container').show()
    $('#image-box__container').attr('class', `item-num-${num}`)
  })

下記の記述は、価格が入力された際に販売手数料、販売利益を計算して出力するものです。
編集した箇所は、2段目、4段目に記述してる内容で、readyメソッドにより画面ロード時に販売手数料、販売利益を計算し、表示させるというものです。

app/assets/javascript/sales_commission.js 
$(function() {
  var input=$("#item_exhibition_price"),fee=1/10,feeIncluded=$("#sales_commission_price");
  input.on("input",function(){
    feeIncluded.text(Math.floor($(this).val() * fee).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_commission_price.append("");
    }
  });
  $(document).ready(function(){
    feeIncluded.text(Math.floor($("#item_exhibition_price").val() * fee).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_commission_price.append("");
    }
  });
});

$(function() {
  var input=$("#item_exhibition_price"),tax=9/10,salesProfit=$("#sales_profit_proce");
  input.on("input",function(){
    salesProfit.text(Math.ceil($(this).val() * tax).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_profit_proce.append("");
    }
  });
  $(document).ready(function(){
    salesProfit.text(Math.ceil($("#item_exhibition_price").val() * tax).toLocaleString());
    if($('sales_commission_price').present!=0){
      sales_profit_proce.append("");
    }
  });
});

これで編集機能完成です。
ここまで読んでくださり、ありがとうございます。

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

Rakefileの動きをRunbookで書く

はじめに

卒業研究の一環として,研究室で使われているPDFを作るためのRakefileを,Runbookの形式で書かく.
- Patrick Blesi, Github, runbook
ここでは書き換えの過程とできたものの使い方を書く.

実行環境

  • MacBook Air
  • MacOS High Sierra(バージョン 10.13.3)
  • Rubyバージョン 2.7.0

Runbookとは

手順を明確にし一つ一つのステップを踏みながら進んでいくプログラム.
RubyのDSLの一つとして段階的に自動化を書いていくことができる.
- Patrick Blesi, Runbook: A Ruby DSL for Gradual System Automation

なぜRakeじゃなくてRunbook?

私のように初心者でCUIを使うことにまだ慣れていない人間からすれば,Rakefileを実行すると勝手に処理が進み何をしているのかがわからなくなる.そこでRunbookを使うことにより手順が明確になり自分が何をしているのかがわかるようになる.
また,Rakefileでは実行途中でおかしく思っても止めることを最初からプログラムに書いていないと難しい,しかし,Runbookであれば細かく分かれた手順の中で毎回ユーザーに入力を求めて続けるかを選ぶことが出来る.

プログラムの概要

Runbookの基本

基本はタイトル,セクション,ステップからなるプログラムで,ステップの中にプログラムを書いていく.
Runbookのコマンドにスタートのための雛形があるのでそれをインストールしてRunbookの基本を確かめる.

> runbook generate runbook my_first_runbook

これによりディレクトリ内にmy_first_runbook.rbが生成される.

my_first_runbook.rb
require "runbook"

runbook = Runbook.book "My First Runbook" do
  description <<-DESC                                                           
This is a runbook that...                                                       
  DESC

  section "SECTION" do
    step "STEP" do
      # Add statements here                                                     
    end
  end
end

if __FILE__ == $0
  Runbook::Runner.new(runbook).run
else
  runbook
end

実行すると下図のように続けるかを聞かれるだけのものが実行される.

> runbook exec my_first_runbook.rb 
Executing My First Runbook...

Description:
This is a runbook that...

Section 1: SECTION

Step 1.1: STEP

Continue? (enter "h" for help) [c,s,j,P,e,h] 
> runbook exec my_first_runbook.rb 
Executing My First Runbook...

Description:
This is a runbook that...

Section 1: SECTION

Step 1.1: STEP

Continue? (enter "h" for help) [c,s,j,P,e,h] c
>> Continue to execute this step
> runbook exec my_first_runbook.rb 
Executing My First Runbook...

Description:
This is a runbook that...

Section 1: SECTION

Step 1.1: STEP

Continue? Continue to execute this step

RakefileをRunbookに移植

研究室使われているRakefileの構成は主に
- orgファイルの読み込み
- latexに変換
- report用の形式にlatexを変更
- platexを用いてPDFの作成

の四つなので,これらをステップとしてRunbookを書く.

make_pdf.rb
Runbook.book "Make PDF" do
  description <<-DESC                                                           
    This is a make PDF from org                                                 
  DESC

  section "Make pdf" do
    step "Load org file" 

    step "Make tex file" 

    step "Load and Convert tex file" 

    step "Make pdf" 
  end
end

実行結果

> runbook exec make_pdf.rb
Executing Make PDF...

Description:
    This is a make PDF from org

Section 1: Make pdf

Step 1.1: Load org file

Continue? Continue to execute this step
Step 1.2: Make tex file

Continue? Continue to execute this step
Step 1.3: Load and Convert tex file

Continue? Continue to execute this step
Step 1.4: Make pdf

雛形ができたのでこれに中身を足していく.

セクションをlatexを作るものとPDFに変換するものに分けたほうが止めやすいと思ったのでそのようにし,中身を足す.

make_pdf.rb
require "./convert"
require "colorize"

Runbook.book "Make PDF" do
  description <<-DESC
    This is a make PDF from org
  DESC

  section "Make latex" do
    $t_file = "report"
    step "Load org file" do
      note "Load org file"
      ruby_command do
        $file = Dir.glob("*.org")[0].match(/(.*).org/)[1]
        puts "your org file is " + $file.red + "."
      end
    end
    step "Make tex file" do
      note "Make tex file"
      ruby_command do
        system "emacs #{$file}.org --batch -f org-latex-export-to-latex --kill"
      end
    end
    step "Load and Convert tex file" do
      ruby_command do
        $lines = File.readlines("#{$file}.tex")
        $lines = convert_thesis($lines)

        File.open("#{$t_file}.tex", "w") do |f|
        $lines.each { |line| f.print line }
      end
    end
  end

  section "Make PDF" do
    step "Make pdf" do
      note "Make pdf"
      ruby_command do
        commands = ["platex #{$t_file}.tex",
                    "bibtex #{$t_file}.tex",
                    "platex #{$t_file}.tex",
                    "dvipdfmx #{$t_file}.dvi"]
        commands.each { |com| system com }
      end
    end
  end
end

これで,"Load org file" でディレクトリ内のorgファイルを読み込み,latexに変換しPDFを作るRunbookのプログラムができた.

  • convert.rb は"Load and Convert tex file"内で使っている,convert_thesisの関数を使うためのプログラム.内容はorgから作成したlatexをレポート用の雛形に変換するものである.
convert.rb
def convert_thesis(lines)
  head = <<'EOS'
  \documentclass[a4j,twocolumn]{jsarticle}
  \usepackage[dvipdfmx]{graphicx}
  \usepackage{url}

  \setlength{\textheight}{275mm}
  \headheight 5mm
  \topmargin -30mm
  \textwidth 185mm
  \oddsidemargin -15mm
  \evensidemargin -15mm
  \pagestyle{empty}


  \begin{document} 

  \title{}
  \author{学科 \hspace{5mm} 学籍番号 \hspace{5mm} your name}
  \date{}

  \maketitle
EOS
  head2 = <<'EOS'
  {\small\setlength\baselineskip{15pt}  % 参考文献は小さめの文字で行間を詰めてある
  \begin{thebibliography}{9}
  \bibitem{} 
  \end{thebibliography}
  }
  \end{document}
EOS
  new_line = [head]
  lines[31..-1].each do |line|
    new_line << line
  end

  new_line.each do |line|
    line.gsub!('\end{document}', head2)
    line.gsub!('\tableofcontents', "")
  end
  return new_line
end

指摘

研究室メンバーからの指摘

  • orgファイルが複数ある場合選べるようにしたい.
  • ディレクトリ内にできたファイル(.auxや.div)が自分で変更することがないのに残っていて見にくいので消したり,まとめたりして欲しい.

orgの選択を"Load org file"のなかで出来るようにする.

make_pdf.rb
require "./convert"
require "colorize"

Runbook.book "Make PDF" do

...

$t_file = "report"
    step "Load org file" do
      note "Load org file"
      ruby_command do
        str = Dir.glob("*.org")
        str.each do |name|
          puts "your org file is " + name.red + " ? (y or n)"
          res = $stdin.gets.chomp

          if res == "y"
            $file = name.match(/(.*).org/)[1]
            break
          elsif res == "n"
            if name == str[str.size - 1]
              puts "This directory not have the objective file".red
              exit
            end
          end
        end
      end
    end

...

end

orgファイルの名前ををメッセージとしてターミナル上に表示し,"y"か"n"を入力することで選べるようにした.全て”n”なら強制終了する.

次に一番最後に"Move report"とし,できたレポートをreportというディレクトリを作り移動させるようにした.

make_pdf.rb
require "./convert"
require "colorize"

Runbook.book "Make PDF" do

...

section "Make PDF" do
    step "Make pdf" do

...

    step "Move report" do
      note "Move report"
      ruby_command do
        commands = ["mkdir report",
                    "mv -f #{$t_file}.* ./report",
                    "open ./report/#{$t_file}.pdf"]
        commands.each { |com| system com }
      end
    end
  end
end

教授からの指摘

  • 入力をせずにノンストップで全てを実行できる方法が無いのかどうかを調べて確認しろとの指摘.

gem RunbookのGithubに載っているマニュアルに記述があった.

> runbook exec --auto my_runbook.rb

これで実行すると入力なしに最後まで実行できる.

使い方

Runbookの導入

gemでインストールする.gem install runbook

実行方法

runbook exec runbook.rbとするとrunbook.rbが実行される.
- runbook.rbは自分のプログラム.
- プログラムのある場所のパスが分かればどのディレクトリでも実行可能

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

ruby on rails : gem install sqlite3 のコマンドが通らない

昨日(2020/6/27)からrailsの勉強をしているのですが、
gem install sqlite3  のコマンドでエラーが起きて困っております・・。

↓エラー文
ERROR: While executing gem ... (Gem::FilePermissionError)
You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.

下記参考動画を真似して環境構築したのですが(←どこかでおかしなことしてしまった?)、うまく環境変数の設定ができておらず、echo $PATH で変数の中身を確認してみると、
/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
と、なっていて、環境変数が上手く設定できていません。

動画のように環境構築進めたのですが、途中でnode.jsをインストールしなさい、的なエラーが出たりで、
何度か環境変数の設定
echo 'eval "$(rbenv init -)"' > ~/.bash_profile
source ~/.bash_profile
を打ち込んだのを覚えています。原因としては.bash_profileの
/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin と2重になっているのを
/usr/local/bin:/usr/local/sbin
に変更できたらgem install sqlite3 のコマンドが通るのかなと思ったのですが、いかがでしょうか?
詳しい方がいらっしゃいましたら、ご教示いただけますと幸いです。
※足りない情報がありましたら申し訳ございません。。

念のため 各バージョンも明記しておきます。

ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]
Rails 6.0.3.2
rbenv 1.1.2
Homebrew 2.4.2

▼参考動画
https://www.youtube.com/watch?v=OHycvUQ4VNQ&t=1431s

【追記】
railsのサーバーを立ち上げたりなどはできております。
※ただ、rails s だと localhost:3000に繋げないので、
rails s -b 0.0.0.0 でつないでいます。

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

Rubyでイーブイを進化させる

こんにちは。そうはぶです。
ちょうど2ヶ月前の4/27プログラミングを始めました。
現在はもっぱらGoを勉強していますが、プログラミングを始めた初日はProgateのRuby講座をやったのでRubyから入りました。
最初にRubyを選んだ理由は、2ヶ月前の未経験の自分でも知っているくらい有名な言語だったので特に深く考えず長いものに巻かれろみたいなノリで選びました。
Ruby講座をおよそ2日間かけて2周しました。後半から全然理解できなかったのをいまでもはっきり覚えています。笑
その後定着させるためすぐに作った初めてのオリジナルアプリがフォルダ整理していたら出てきたので記録として残しておきます。

コード

その名も「イーブイ」です。そうですあのイーブイです。イーブイに水の石と炎の石どちらを使うか選んで進化させて、それと同時に新しい技を覚えるというだけのアプリです笑。
コードがこちらです。

eevee.rb
class Pokemon

    attr_accessor :name, :sex, :skill

    def initialize(name:, sex:, skill:)
        @name = name
        @sex = sex
        @skill = skill
    end

    def who
        "初めまして。#{@name}だよ。\n僕の性別は#{@sex}だよ。\n必殺技は#{skill}だよ。"
    end

end

eevee = Pokemon.new(
    name: 'イーブイ',
    sex: 'オス',
    skill: 'しっぽをふる'
)

puts eevee.who



class Evolution < Pokemon
    attr_accessor :new_name

    def name_change
        @new_name = "hanako"
    end

    def who_e
        "おめでとう!イーブイは#{name}に進化したよ!\n新しく#{skill}を覚えたよ!"
    end

end

fires = Evolution.new(
    name:'ファイアーズ',
    sex: nil,
    skill:'かえんほうしゃ'
)

showers = Evolution.new(
    name:'シャワーズ',
    sex:nil,
    skill:'れいとうびーむ'
)

puts "どの石を使いますか?\n選択してください。\n\n1.炎の石\n2.水の石"

answer = gets.chomp.to_i

if answer == 1
    puts fires.who_e

elsif answer == 2
    puts showers.who_e
end


#=================================@nameに入力した名前を表示を代入できなかった。==================

# puts "ニックネームをつけますか?\n選択してくだい。\n\n1.つける\n2.つけない"
# nickname_answer = gets.chomp.to_i

# if nickname_answer == 1 && answer ==1
#     puts "新しい名前を入力してください。"
#     fires.name_change
#     puts "名前が#{@new_name}に変わったよ!"
# elsif nickname_answer == 1 && answer ==2
#     puts "新しい名前を入力してください。"
#     showers.name_change
#     puts "名前が#{@new_name}に変わったよ!"
# else
#     puts "終わりです。"
# end

感想

実際のポケモンみたいにニックネームを付けようとしたもののつけることができなくて苦労したコメントまでついてました。笑
このときは頭混乱してなぜかできたと言う感じだったので、2ヶ月前に比べると自分も成長したなあと感じています。
5月頭にRailsでツイッター風アプリを作ってからはGoに乗り換えてずっとGoをやっていますが、またRubyも完全に忘却する前に触りたいです。

参考

Rubyの書籍はこれのKindle版しか買いませんでした。すぐ乗り換えてしまったためあまり読めてないですが、Progateを終えたばかりの自分にはちょうどよかったです。

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

【備忘録】本番環境への更新[heroku]

はじめに

本番環境へ更新する時、毎回緊張しているたかしです。
今回はherokuで初の本番環境へ更新しました。
初めてということで案の定エラーが出て焦りまくりだったんですが、原因はとても簡単でした。。

今回はその解決方法を自分が忘れないためにも、他の人がこのエラーに遭遇してすぐ解決できるように記事として投稿いたします。

エラー画面

6ae4049abac24c612af7c9f06c3aa624.png

現状では、pushできる環境ではありません的なことを言っています。

解決方法

herokuでのデプロイはgitと連動しているため、変更内容があった場合は下記コマンドを実行して変更内容を反映できるようにしましょう。

$ git add
$ git commit -m "Update application"
$ git push heroku master

#マイグレーションの変更等をした際は下記コマンド実行
 heroku run rails db:migrate

終わりに

上記のとおりすごい簡単な話でした。
毎度本番環境には変な緊張感を持ってしまいます。

手順的にはすごい簡単なんですが、もしつまずいてる人がいたら是非参考にしてみてください。

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

gem 'rails-i18n', '~> 6.0.0'�を追加し、bundle install でエラーが出た時の解決方法

エラーの出現

Rails5でエラーメッセージの日本語化をするために
Gemfileに下記を追加

gem 'rails-i18n', '~> 6.0.0'

次に、ターミナルにて

$ bundle install

を実行したところ、下記のエラーが発生しインストールができない。

The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
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:
    coffee-rails (~> 4.2) was resolved to 4.2.2, which depends on
      railties (>= 4.0.0)

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

    rails-i18n (~> 6.0.0) was resolved to 6.0.0, which depends on
      railties (>= 6.0.0, < 7)

    sass-rails (~> 5.0) was resolved to 5.1.0, which depends on
      railties (>= 5.2.0)

    web-console (>= 3.3.0) was resolved to 3.7.0, which depends on
      railties (>= 5.0)

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

エラー原因

gem 'rails-i18n', '~> 6.0.0'

のバージョン指定が間違っていた。
上記バージョンはrails6の場合だった。

解決方法

今回のrailsのバージョンはrails5だったので、その場合は、

gem 'rails-i18n', '~> 5.1'

とすればOK!!

その後、

$ bundle install

を実行したら無事インストールできた。

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

ある範囲内の整数にカプレカ数があるかRubyで調べてみた

背景

高校までは数学が一番得意で、大好きでした。
なのに文系の大学に入った私は数学とは縁遠い生活を送り
そして社会人になり早十年…
微積分も三角関数も忘れてしまった左脳だけ発達してる系エンジニアですが、
最近はYouTubeのおかげで数学に触れる機会が少しずつ増えました。
入試問題を解いたり、知られざる大学数学の世界を気軽に触れられるいい時代です。

本日は以下の動画で紹介されていた面白い性質を持った整数「カプレカ数」について
Rubyを使って検証していきます。

※元ネタ
予備校のノリで学ぶ「大学の数学・物理」
最大から最小を引いて元通り!【カプレカ数】

カプレカ数とは

カプレカ数(カプレカすう、Kaprekar Number)とは、次のいずれかで定義される整数である。
1. 2乗して前の部分と後ろの部分に分けて和を取ったとき、元の値に等しくなるもの。
2. 桁を並べ替えて最大にしたものから最小にしたものの差を取ったとき、元の値に等しくなるもの。

引用元:Wikipedia

今回調べるのは定義2の方です。

例えば495を例に考えてみます。

495を並べ替えて作れる最大の数は954、最小の数は459です。
954 - 459 = 495ですから、元の数字495と等しくなりました!
面白いですね!

このような数字をカプレカ数といい、
我々が普段目にする桁数の数字では数えられるほどしかありません。

では本題のRubyプログラムに入っていきましょう。

任意の整数がカプレカ数かどうかを判定する

kaprekar.rb
num = 627

# 配列に格納
arr = num.to_s.split('').map!(&:to_i)
p arr   # => [6, 2, 7]

# 並べ替えて最大と最小を作る
max = arr.sort{ |a, b| a <=> b }.join('').to_i
min = arr.sort{ |a, b| b <=> a }.join('').to_i
p max   # => 762
p min   # => 267

# 最大 - 最小が元の数字と同じかどうかの判定
diff = max - min
p diff == num   # => false

これでnumを書き換えることで任意の整数1つのみの判定を行えるようになりました。

特定の範囲の中にカプレカ数かどうかを判定する

ここから繰り返し処理を用いて特定の範囲の中にカプレカ数があるかどうかを探してみましょう。

kaprekar.rb
result = []
num = 1
final = 1_000_000

while num < final do
  arr = num.to_s.split('').map!(&:to_i)
  min = arr.sort{ |a, b| a <=> b }.join('').to_i
  max = arr.sort{ |a, b| b <=> a }.join('').to_i
  diff = max - min

  if diff == num
    result.push("【#{num}#{max} - #{min} = #{diff}")
  end

  num += 1
end

if result.empty?
  result.push("該当なし")
end

puts result

(可読性重視で敢えて三項演算子は使っていません)

terminal
【495】 954 - 459 = 495
【6174】 7641 - 1467 = 6174
【549945】 995544 - 445599 = 549945
【631764】 766431 - 134667 = 631764

上記例では6桁の整数までで試したところ、4つのカプレカ数が見つかりました。
5桁には存在しないというのもまた不思議!

余談

PCのスペックにもよりますが、100万回も繰り返し処理したら結構時間かかります。

冒頭で紹介した495という数字を見て
495 = 99 × 5がすぐに思いついたあなたは数学的センスをお持ちですね。
そう、カプレカ数はどれも9の倍数です。

なので100万回やらなくても、9の倍数のみ検証すればもっと処理は軽減できます。

もっといい書き方がありましたらコメントで教えてください!

(2020/06/29 追記)
早速コメントをいただき改良版をコメント欄に投稿しました!

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

Ruby と Python で解く AtCoder ABC172 C 累積和 二分探索

はじめに

AtCoder Beginner Contest 172 に参加しました。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Beginner Contest C - Tsundoku
Difficulty: 878

今回のテーマ、累積和 + 二分探索

最初は、動的計画法と考え解いていましたがTLEを貰い大幅な時間のロス、二分探索に切り替えました。
入力例1ですと、次の表の各行で、机Aと机Bの累積和がK分を超えない本数の最大値を求めます。

1 2 3 4 5 6 7
60 90 120 80 150 80 150
60 90 80 150 80 150
60 80 150 80 150
80 150 80 150

Ruby

ruby.rb
n, m, k = gets.split.map(&:to_i)
a = gets.split.map(&:to_i)
b = gets.split.map(&:to_i)
c = Array.new(n + 1, 0)
n.times do |i|
  c[i + 1] = c[i] + a[i]
end
d = Array.new(m + 1, 0)
m.times do |i|
  d[i + 1] = d[i] + b[i]
end
cnt = 0
0.upto(n) do |i|
  break if c[i] > k
  e = d.bsearch_index{_1 > k - c[i]}
  if e.nil?
    cnt = i + m if cnt < i + m
  else
    cnt = i + e - 1 if cnt < i + e - 1
  end
end
puts cnt
ruby.rb
c = Array.new(n + 1, 0)
n.times do |i|
  c[i + 1] = c[i] + a[i]
end
d = Array.new(m + 1, 0)
m.times do |i|
  d[i + 1] = d[i] + b[i]
end

累積和です。

ruby.rb
  e = d.bsearch_index{_1 > k - c[i]}
  if e.nil?
    cnt = i + m if cnt < i + m
  else
    cnt = i + e - 1 if cnt < i + e - 1
  end

二分探索です。配列の最大値を超えた場合、nilを返しますので、その処理が入っています。

Python

python.py
from sys import stdin
import bisect

def main():
    input = stdin.readline
    n, m, k = map(int, input().split())
    a = list(map(int, input().split()))
    b = list(map(int, input().split()))
    c = [0] * (n + 1)
    for i in range(n):
        c[i + 1] = c[i] + a[i]
    d = [0] * (m + 1)
    for i in range(m):
        d[i + 1] = d[i] + b[i]
    cnt = 0
    for i in range(n + 1):
        if c[i] > k:
            break
        e = bisect.bisect_right(d, k - c[i])
        if cnt < i + e - 1:
            cnt = i + e - 1
    print(cnt)
main()
python.py
        e = bisect.bisect_right(d, k - c[i])
        if cnt < i + e - 1:
            cnt = i + e - 1

二分探索です。配列の最大値を超えた場合、配列の要素数+1を返します。

Ruby Python
コード長 (Byte) 433 570
実行時間 (ms) 349 246
メモリ (KB) 44860 47636

まとめ

  • ABC 172 C を解いた
  • Ruby に詳しくなった
  • Python に詳しくなった

参照したサイト
bisect --- 配列二分法アルゴリズム

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