file_filedの注意点

今回、以下のformで画像をupすることにして、空で送ってしまったときはバリデーションにひっかかるように設定しようと思った。

<%= form_with(model: @user_icon, local: true) do |form| %>
  <%= form.file_field :image %>
  <%= form.submit "更新する" %>
<% end %>

しかし、これで送信すると、以下のようにparameterにrequire(:user_icon)のkeyがそもそもないため、コントローラーのstrong_paramsでエラーになってしまう。
user_icons_controller.rb

def create
    @user_icon = UserIcon.new(user_icon_params)
    if @user_icon.save
      redirect_to user_path(@user_icon.user_id), notice: 'アイコンを設定しました'
    else
      render 'users/show'
    end
  end
#省略
private
  def user_icon_params
    params.require(:user_icon).permit(:user_id, :image)
  end

エラー詳細

[2] pry(#<UserIconsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"62WAR0vsxA+HqJabeXbmOOOCF7TfE+x9WLp0yXFOTes2+dFb8AHh0hq36kOsR7eH4zmbk8GvSjdqdAgvKwjnjA==", "commit"=>"更新する", "controller"=>"user_icons", "action"=>"create"} permitted: false>

とりあえずは、下記のようにform.file_fieldタグの他に、今回アソシエーションで紐づいて必要だったuser_idをhiddenタグをparameterに仕込むことで解決できた。

<%= form_with(model: @user_icon, local: true) do |form| %>
  <%= form.hidden_field :user_id, value: "#{current_user.id}" %>
  <%= form.file_field :image %>
  <%= form.submit "更新する" %>
<% end %>

ただ、今までnameやemailを全て空欄で送信してもバリデーションにひっかけることができたのに、なぜ今回はkey(この例では:user_icon)が設定されないのか不思議である。

RailsでAjaxとjQueryを使ってコメントの編集方法

Ajax化概要

ビューのフォームでアクションを指定し、コントローラーでそれを実行するのだが、remote: trueが記入されていることで、そのアクション名.js.erbというファイルが実行され、その中にjqueryコードを書いておく。
そのjqueryコードが実行され、実際にビューに反映されるものはrubyのコードなので大抵、パーシャルhtml.erbに書き込まれrenderされる。

準備

今回、このコメントの編集と削除をjqueryajaxを使って実装していく。

f:id:shoheimoment:20171209154022p:plain

まず、初めのビューファイル view/pictures/index.html.erb

<div class="other_comments">
  <% if picture.other_comments.present? %>
    <% all_other_comments = picture.other_comments.order(created_at: :asc) %>
    <% all_other_comments.each do |each_other_comment| %>
      <div class="for_edit_comment">
        <%= render 'other_comments/each_other_comment', each_other_comment: each_other_comment %>
      </div>
    <% end %>
  <% end %>

render部分 view/other_coments/_each_other_comment.html.erb

<div class="each_other_comment row" data-comment="<%= each_other_comment.id %>">
  <p class="col-xs-10"><strong><%= each_other_comment.user.name %></strong>&nbsp;&nbsp;<span id="for_edit"><%= each_other_comment.other_comment %></span></p>
  <div class="col-xs-2">
    <div class="dropdown">
      <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
      <span class="caret"></span>
      </button>
      <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
      <li><%= link_to '編集', edit_other_comment_path(each_other_comment.id), remote: true %></li>
      <li><%= link_to '削除', other_comment_path(each_other_comment.id),
      remote: true, data: { confirm: '削除しますか?' },
      method: :delete %>
      </li>
      </ul>
    </div>
  </div>
</div>

上の写真では写ってはいないがマウスオーバーすると編集削除ボタンがでる。

f:id:shoheimoment:20171209161705p:plain

 編集ボタンを押すとフォームに変わる

_each_other_comment

<li><%= link_to '編集', edit_other_comment_path(each_other_comment.id), remote: true %></li>

ここのremote: trueを加えることにより、ajaxで行うことを意味する。

※フォームに直すのはjqueryだけでできるが、要素の取得がajaxでやれば簡単だったので今回はajaxで行う。 編集ボタンを押すとother_commentコントローラーのeditアクショへ行く。

other_comments_controller.rb #edit

def edit
    @other_comment = OtherComment.find(params[:id])
    respond_to do |format|
      format.html { redirect_to pictures_path }
      format.js { @id_comment = @other_comment.id }
    end
  end

ajaxで命令が来ると、format.jsが適応されて、デフォルトでview/other_comments/edit.js.erbが呼ばれて実行される。

view/other_comments/edit.js.erb

var idComment = <%= @id_comment %>;

$('.row[data-comment="'+idComment+'"]').parent().html("<%= escape_javascript(render 'edit_other_comment') %>");

他の人のコメントはeach文繰り返し処理を行っているので、classやidだけで要素を取得するのが難しい。そこであらかじめdivタグにdata-comment属性にそのレコードのid値を持たせておく。
この部分 view/other_coments/_each_other_comment.html.erb

<div class="each_other_comment row" data-comment="<%= each_other_comment.id %>">

それを元に取得している。 これがajaxでこの処理を行った理由。

なので、edit.js.erbでは取得した要素の一つ上の親要素(for_edit_comment)を取得してhtmlをrender先の_edit_other_commentに書き換えなさいという意味。
※ここでparent()メソッドを使っているのは、html()メソッドは取得した要素の子孫要素を書き換えるメソッドであり、その要素自体を取得して書き換えると、コメントを編集する度に取得した要素が加えられていってしまい、cssがおかしくなったりするから。

次にedit.js.erbからrenderされる部分
_edit_other_comment

<div class="each_other_comment row" data-comment="<%= @id_comment %>">
  <%= form_with(model: @other_comment, remote: true) do |form| %>
    <div class="add_other_comment row">
      <%= form.text_area :other_comment, rows: 3, placeholder: "コメントを追加...", class: "col-xs-11" %>
      <%= form.submit '投稿する', data: { confirm: '投稿しますか?' }, class: 'btn btn-default' %>
    </div>
  <% end %>
</div>

これで編集ボタンを押すとフォームに切り替わる。 f:id:shoheimoment:20171209164103p:plain

コメントを更新する

ここからコメントを更新する作業である _edit_other_comment

 <%= form_with(model: @other_comment, remote: true) do |form| %>

この部分のremote: trueでajax化している。先ほどと同様にコントローラーでDBの命令を出すと同時にupdate.js.erbが実行される。

other_comments_controller.rb #update

def update
    @other_comment = OtherComment.find(params[:id])
    respond_to do |format|
      if @other_comment.update(other_comment: params[:other_comment][:other_comment])
        format.html { redirect_to pictures_path }
        format.js { @id_comment = @other_comment.id }
      else
        format.html { render 'pictures/index' }
        format.js { @status = 'fail' }
      end
    end
  end

update.js.erb

var idComment = <%= @id_comment %>;

$('.row[data-comment="'+idComment+'"]').parent().html("<%= escape_javascript(render 'each_other_comment', each_other_comment: @other_comment) %>");

編集の時と同様に、取得した要素のhtmlを書き換えている。 render先は上の方で書いているview/other_coments/_each_other_comment.html.erbである。 これで投稿ボタンを押すとajaxで更新される。

f:id:shoheimoment:20171209170119p:plain

まとめ

link_toのedit→#edit→edit.js.erb→edit_other_comment.html.erb→元のビューに反映されてフォームの記入ができるようになる。→#update→update.js.erb→each_other_comment.html.erbで更新される

スクレイピングして選択肢を抜き出して、選択ボックスを作る方法

  1. ソースコードからスクレピングで抜き取りたい項目を抜き取る。
  2. ハッシュに追加していく。
  3. それを使って、選択ボックスを作成

helper.rb

module ConditionsHelper
  #diverの質問投稿のソースコードから必要なものをdocフォルダの中のそれぞれのファイルにコピペする
  #それの読み取り
  def read_original_lists(original)
    file = File.open("#{original}")
    text = file.read
    file.close
    return text
  end

  #タイトルの抜き出し
  def get_title(original_lists)
    #value=のあとの">"から探せばいいから、それのインデックスを求めて、なければnilを入れループを終了
    close_number = original_lists.index("value=")
    if close_number == nil
      return nil,0
    end
    #タイトルの抜き出し
    start_number = original_lists.index(">", close_number)
    end_number = original_lists.index("<", start_number + 1)
    title = original_lists.slice(start_number+1..end_number-1)
    return title, end_number
  end

  def make_lists(original)
    material_lists = Hash.new
    each_number = 1
    original_lists = read_original_lists(original)

    while true
      title, end_position = get_title(original_lists)
      #value=以下の">"がない、すなわちタイトルが抜き出し終わったらbreak
      break if title == nil
      material_lists["#{title}"] = each_number
      each_number += 1
      #最初にファイルから抜き取ったテキストから取得したタイトル部分を抜き取っていき、どんどん少なくしていく。
      original_lists = original_lists.slice(end_position..-1)
    end

    return material_lists
  end

  #新しくconditionレコードを作成するときの選択肢。どこまで終わったかを選択
  def choose
    original = "doc/english_original_lists.txt"
    return make_lists(original)
  end

  #全体の進捗を計算するために全体のページ数を計算
  def number_of_pages
    original = "doc/japanese_original_lists_by_phase3.txt"
    return ((make_lists(original)).keys.length)
  end
end

view.html.erb

<%= form.select :progress, choose,
            { include_blank: 'Please choose' } %>

CSS テキストの折り返しスクロール方法

■white-space: pre-wrap
改行や半角スペースがブラウザ表示にそのまま反映
要素の端で行が折り返される

■overflow: scroll
スクロールできるように

■word-wrap: break-word
行の端で自動折り返し

ActiveRecordでsaveメソッドの表記

疑問点  

何か新しいレコードを作りたい時、同時に違うテーブルにもレコード作りたい時に下記のようにコントローラーに書けば登録できるが、きたない。

condition = Condition.new
condition.student_id = @student.id
condition.progress = 0
condition.date = Date.today
condition.comment = "Account created"
condition.save

これをハッシュみたいにして綺麗にかけないの?
下のようにハッシュで書いても入力されない。

 condition.save({
    "student_id"=>"#{@student.id}",
     "progress"=>"0",
    "date"=>"#{Date.today}",
    "comment"=>"Account created",
    "username"=>"#{current_user.username}"
})

解決策

これはrails5からstrong_paramsからでないとレコードにsaveできなくなったらしい。 上記は初期値を設定したかったから、アソシエーションを使って、

condition = @studnet.conditions.new
condition.save

これでstudentに紐づいた新しいレコード作成し、 DBのカラムにdefaultオプションを使って初期値を設定することで解決。 ただ、レコードを一から作る羽目に。涙

アソシエーションのthroughとsource

models/user.rbのアソシエーション設定で

has_many :blogs, through: :favorites

↑これを

has_many :favorite_blogs, through: :favorites, source: :blog 

のように書いたら、user.blogsだけでお気に入りを全部取得する。

user.blogsでユーザーが書いた全てのブログを取得でき、user.favorite_blogsでユーザーがお気に入りしたブログを全て取得できる

前者だとuser.blogsでユーザーがお気に入りしたブログを全て取得。
なので、後者のほうが自由度が高い。

bcrypt bundle install エラー

gem 'bcrypt', '3.1.11'をbundle installを実行すると下記のエラーが発生

sesion_login $gem install bcrypt -v '3.1.11'
Building native extensions.  This could take a while...
ERROR:  Error installing bcrypt:
    ERROR: Failed to build gem native extension.

    current directory: /Users/Shohei/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bcrypt-3.1.11/ext/mri
/Users/Shohei/.rbenv/versions/2.3.0/bin/ruby -r ./siteconf20171119-7774-edh4j6.rb extconf.rb
creating Makefile

current directory: /Users/Shohei/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bcrypt-3.1.11/ext/mri
make "DESTDIR=" clean
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

current directory: /Users/Shohei/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bcrypt-3.1.11/ext/mri
make "DESTDIR="
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

make failed, exit code 1

Gem files will remain installed in /Users/Shohei/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bcrypt-3.1.11 for inspection.
Results logged to /Users/Shohei/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-16/2.3.0-static/bcrypt-3.1.11/gem_make.out

ここを見て、 ruby - bcrypt-3.1.11 error when runing bundle install - Stack Overflow

まず、xcode-select --installをしてbundle installをしたらできた。