Skip to content

Commit

Permalink
Merge pull request #13 from kudojp/topic-food-items
Browse files Browse the repository at this point in the history
MealPostのサブ項目としてFoodItemを追加する
  • Loading branch information
kudojp authored Jun 18, 2020
2 parents d4c5e91 + 2b4b67b commit 5df7742
Show file tree
Hide file tree
Showing 39 changed files with 406 additions and 33 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ gem 'devise'

gem 'omniauth-facebook'
gem 'dotenv-rails'
gem 'cocoon'
gem 'counter_culture'

group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
Expand Down
10 changes: 10 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ GEM
zeitwerk (~> 2.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
after_commit_action (1.1.0)
activerecord (>= 3.0.0)
activesupport (>= 3.0.0)
ast (2.4.0)
bcrypt (3.1.13)
bindex (0.8.1)
Expand All @@ -74,7 +77,12 @@ GEM
regexp_parser (~> 1.5)
xpath (~> 3.2)
childprocess (3.0.0)
cocoon (1.2.14)
concurrent-ruby (1.1.6)
counter_culture (2.3.0)
activerecord (>= 4.2)
activesupport (>= 4.2)
after_commit_action (~> 1.0)
crass (1.0.6)
devise (4.7.1)
bcrypt (~> 3.0)
Expand Down Expand Up @@ -281,6 +289,8 @@ DEPENDENCIES
bootsnap (>= 1.4.2)
byebug
capybara (>= 2.15)
cocoon
counter_culture
devise
dotenv-rails
factory_bot_rails
Expand Down
Empty file removed app/controllers/concerns/.keep
Empty file.
5 changes: 5 additions & 0 deletions app/controllers/concerns/ajax_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module AjaxHelper
def ajax_redirect_to(redirect_uri)
{ js: "window.location.replace('#{redirect_uri}')" }
end
end
1 change: 1 addition & 0 deletions app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ def index
@user = current_user
@meal_posts = @user&.meal_posts_feed&.includes(:user)
@new_meal_post = @user&.meal_posts&.new
3.times { @new_meal_post&.food_items&.build }
end
end
58 changes: 53 additions & 5 deletions app/controllers/meal_posts_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
class MealPostsController < ApplicationController
before_action :authenticate_user!, only: %i[create destroy]
include AjaxHelper
before_action :authenticate_user!, only: %i[create destroy update]

def create
@new_meal_post = current_user.meal_posts.build(meal_post_params)
@new_meal_post = current_user.meal_posts.build(new_meal_post_params)

# meal_post object used for rendering partial form after successfully creating meal_post
@next_meal_post = current_user.meal_posts.new
3.times { @next_meal_post&.food_items&.build }

if @new_meal_post.save
return respond_to do |format|
Expand All @@ -17,7 +22,43 @@ def create
# TODO: errorを伝搬するなりして、alertをもう少しdescriptiveにする
format.html { redirect_to root, alert: 'You could not make a meal post.' }
format.js do
render partial: 'meal_posts/create_failure', status: :bad_request
render 'meal_posts/create_failure', status: :bad_request
end
end
end

def update
@meal_post = MealPost.find(params[:id])
unless @meal_post.user_id == current_user.id
respond_to do |format|
format.html { redirect_to root_path, alert: 'You are not authorized to update the post' }
# (ajaxなど)js-acceptのリクエストでこれが叩かれることは基本ないだろうが、、、
format.js { render ajax_redirect_to(root_path) }
end
end

is_updated = @meal_post.update(update_meal_post_params)
# 以下の理由でreloadが必要である
# (1)counter_cultureで更新される@meal_post.total_caloriesの値をモデルに反映するため
# (2)@meal_post.food_itemsのそれぞれのmark_for_destructionをfalseに戻すため
# ここで設定しないと以下のようなバグが生じる
# [バグ]food_itemsを全て消して投稿した後に、画面描写後再び投稿ボタンを押すと「Food items should have at least 1 food item.」というエラーが出る
@meal_post.reload

if is_updated
return respond_to do |format|
# TODO: friendly forwardingを実装
format.html { redirect_to root, notice: 'You have successfully updated a meal post.' }
format.js
end
end

respond_to do |format|
# TODO: friendly forwardingを実装
# TODO: errorを伝搬するなりして、alertをもう少しdescriptiveにする
format.html { redirect_to root, alert: 'You could not update a meal post.' }
format.js do
render 'meal_posts/update_failure', status: :bad_request
end
end
end
Expand All @@ -36,6 +77,7 @@ def destroy

def show
@meal_post = MealPost.find(params[:id])
@is_own_meal_post = @meal_post.id == current_user.id
end

def upvoted_index
Expand All @@ -51,7 +93,13 @@ def downvoted_index

private

def meal_post_params
params.require(:meal_post).permit(:content, :time)
def new_meal_post_params
params.require(:meal_post).permit(:content, :time, food_items_attributes: %i[name amount calory _destroy])
end

def update_meal_post_params
# patchメソッドだとRailsアプリケーションでrequest_bodyの配列をうまく解釈できない(hashになる)ため
params[:meal_post][:food_items_attributes] = params[:meal_post][:food_items_attributes].values
params.require(:meal_post).permit(:content, :time, food_items_attributes: %i[name amount calory _destroy id])
end
end
10 changes: 10 additions & 0 deletions app/helpers/meal_posts_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
module MealPostsHelper
def total_calories_of(meal_post)
return '- kcal' if meal_post.food_items_with_calories_count.zero?
return "#{meal_post.total_calories}kcal+" if meal_post.food_items_count != meal_post.food_items_with_calories_count

"#{meal_post.total_calories}kcal"
end

def total_count_and_calories_of(meal_post)
"#{meal_post.food_items_count}#{total_calories_of(meal_post)}"
end
end
1 change: 1 addition & 0 deletions app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import "../stylesheets/application.scss";

import "flatpickr/dist/flatpickr.css";
import flatpickr from "flatpickr";
import 'cocoon-js'

require("../includes/vote.js")

Expand Down
5 changes: 3 additions & 2 deletions app/javascript/stylesheets/application.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
@import '~bootstrap/scss/bootstrap';
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import './signup.scss';
@import './destroy_confirmation.scss';
@import './devise/signup.scss';
@import './users/destroy_confirmation.scss';
@import './meal_posts/_food_item_fields.scss';

.upvote,
.downvote {
Expand Down
File renamed without changes.
5 changes: 5 additions & 0 deletions app/javascript/stylesheets/meal_posts/_food_item_fields.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.nested-fields {
.fi_field {
width: 30%;
}
}
19 changes: 19 additions & 0 deletions app/models/food_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class FoodItem < ApplicationRecord
belongs_to :meal_post
counter_culture :meal_post
counter_culture :meal_post, column_name: proc { |model| model.calory.nil? ? nil : 'food_items_with_calories_count' },
column_names: { ['food_items.calory IS NOT NULL'] => 'food_items_with_calories_count' }
counter_culture :meal_post, column_name: 'total_calories', delta_column: 'calory'

before_validation :strip_whitespaces
validates :name, presence: true, length: { maximum: 30 }
validates :amount, length: { maximum: 30 }, allow_blank: true
validates :calory, numericality: { only_integer: true, greater_than: 0 }, allow_blank: true

private

def strip_whitespaces
name&.strip!
amount&.strip!
end
end
3 changes: 3 additions & 0 deletions app/models/meal_post.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class MealPost < ApplicationRecord
belongs_to :user
has_many :votes, dependent: :destroy
has_many :food_items, dependent: :destroy
accepts_nested_attributes_for :food_items, reject_if: proc { |attr| attr['name'].blank? && attr['amount'].blank? && attr['calory'].blank? }, allow_destroy: true

has_many :upvotes, -> { where(is_upvote: true) }, class_name: 'Vote'
has_many :downvotes, -> { where(is_upvote: false) }, class_name: 'Vote'
Expand All @@ -15,6 +17,7 @@ class MealPost < ApplicationRecord
validates :user_id, presence: true
validates :time, presence: true
validates :content, presence: true, length: { maximum: 200 }
validates :food_items, length: { minimum: 1, message: 'should have at least 1 food item.' }

def score
# TODO: 負荷が高いのでバッチ処理化 or SQLの集計関数を使う
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class User < ApplicationRecord
has_many :favorite_meal_posts, through: :upvotes, source: :meal_post
has_many :unfavorite_meal_posts, through: :downvotes, source: :meal_post

before_validation :strip_whitespaces, only: %i[name account_id]
before_validation :strip_whitespaces
validates :name, presence: true, length: { in: 1..20 }
validates :account_id, presence: true, length: { in: 5..15 }, uniqueness: true,
format: { with: /\A[A-Za-z0-9]+\z/, message: 'should be alphanumeric' }
Expand Down
9 changes: 5 additions & 4 deletions app/views/home/_signed_in_home.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
<h1 class='row mb-5'><%= "Welcome back, #{@user.name}!" %></h1>
<div class='row'>
<div class='col-4'>
<div class='col-2'>
<p><%= link_to 'My Profile', user_path(@user) %></p>
<p><%= link_to 'Followings', followings_user_path(@user) %></p>
<p><%= link_to 'Followers', followers_user_path(@user) %></p>
<p><%= link_to 'Your Upvotes', upvotes_user_path(@user) %></p>
<p><%= link_to 'Your Downvotes', downvotes_user_path(@user) %></p>
</div>

<div class='col-4'>
<div class='col-5'>
<%= render template: 'meal_posts/index', locals: {title: 'Your Timeline', meal_posts: meal_posts} %>
</div>

<div class='col-4'>
<%= render partial: 'meal_posts/new'%>
<div class='col-5 mealpost-new'>
<h3>食べたものを記録</h3>
<%= render partial: 'meal_posts/new', locals: {new_meal_post: @new_meal_post} %>
</div>
</div>
1 change: 0 additions & 1 deletion app/views/meal_posts/_create_failure.js.erb

This file was deleted.

32 changes: 32 additions & 0 deletions app/views/meal_posts/_edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div class='edit_meal_post'>
<% if meal_post.errors.any? %>
<div class="meal_post_errors">
<% meal_post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</div><br>
<% end %>

<%= form_for(meal_post, html: { method: :patch }, remote: true) do |f| %>
<div class="field">
<%= f.text_field :content, placeholder: 'メニュー名' %>
</div><br>

<div class="fields">
<%= f.fields_for :food_items do |fi_field| %>
<%= render partial: 'meal_posts/food_item_fields', locals: {f: fi_field} %>
<% end %>
<div class='links'>
<%= link_to_add_association '', f, :food_items, partial: 'meal_posts/food_item_fields', class: "fas fa-plus-circle" %>
</div>
</div><br>

<div class="field">
<%= f.text_field :time, class: 'datetimepicker', placeholder: '食べた日時を選択' %>
</div>

<!-- TODO: 画像もアップロードできるようにする -->

<%= f.submit "投稿する" %>
<% end %>
</div>
6 changes: 6 additions & 0 deletions app/views/meal_posts/_food_item_fields.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class='nested-fields'>
<%= f.text_field :name, placeholder: '品目', class: 'fi_field' %>
<%= f.text_field :amount , placeholder: '分量', class: 'fi_field' %>
<%= f.number_field :calory, placeholder: 'カロリー', class: 'fi_field' %>
<%= link_to_remove_association '', f, class: "fas fa-times-circle" %>
</div>
15 changes: 9 additions & 6 deletions app/views/meal_posts/_meal_post.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@
<%= link_to meal_post.user.name, user_path(meal_post.user) %>
</div>

<div class="row">
<!-- TODO:Fontawesome, TODO: Vote arrow should be pushable only when it is not current user's post -->
<div class="row">
<!-- TODO:Fontawesome, TODO: Vote arrow should be displayed only when it is not current user's post -->
<div class="col-2 text-center">
<%= render partial: 'votes/vote', locals: { meal_post: meal_post } %>
</div>

<div class="col-10">
<div><%= link_to meal_post.content, meal_post_path(meal_post) %></div>
<div>
<%= link_to meal_post.content, meal_post_path(meal_post) %>
(<%= total_count_and_calories_of(meal_post) %>)
</div>
<div class="small"><%= meal_post.time %></div>
<div>
<%= link_to '評価してる人', meal_post_upvoters_path(meal_post) %>
<%= link_to '呆れてる人', meal_post_downvoters_path(meal_post) %>
<%= link_to '高評価した人', meal_post_upvoters_path(meal_post) %>
<%= link_to '低評価した人', meal_post_downvoters_path(meal_post) %>
</div>
<div>
<% if current_user == meal_post.user %>
<%= link_to "Delete this post", meal_post_path(meal_post),
<%= link_to "投稿削除", meal_post_path(meal_post),
method: "delete", remote: true %>
<% end %>
</div>
Expand Down
23 changes: 14 additions & 9 deletions app/views/meal_posts/_new.html.erb
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
<!-- partial for posting new meal -->

<%= form_for @new_meal_post, remote: true do |f| %>
<%= form_for new_meal_post, remote: true do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_field :content %>
</div>
<%= f.text_field :content, placeholder: 'メニュー名' %>
</div><br>

<div class="fields">
<%= f.fields_for :food_items do |fi_field| %>
<%= render partial: 'meal_posts/food_item_fields', locals: {f: fi_field} %>
<% end %>
<div class='links'>
<%= link_to_add_association '', f, :food_items, partial: 'meal_posts/food_item_fields', class: "fas fa-plus-circle" %>
</div>
</div><br>

<div class="field">
<%= f.label :time %>
<%= f.text_field :time, class: 'datetimepicker' %>
<%= f.text_field :time, class: 'datetimepicker', placeholder: '食べた日時を選択' %>
</div>

<!-- TODO: 画像もアップロードできるようにする -->

<%= f.submit "Submit" %>
<%= f.submit "投稿する" %>
<% end %>
8 changes: 8 additions & 0 deletions app/views/meal_posts/_total_calories.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class='total_calories'>
<h4>
総カロリー
</h4>
<h1>
<%= total_calories_of(meal_post) %>
<h1>
</div>
9 changes: 8 additions & 1 deletion app/views/meal_posts/create.js.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
console.log("post successfully created");
// TODO: prependではなくtimeの順番にしたがってしかるべきところに挿入する
<%# TODO: prependではなくtimeの順番にしたがってしかるべきところに挿入する %>
$(".meal-posts").find(".card").first().prepend("<%= escape_javascript(render(partial: 'meal_posts/meal_post', locals: {meal_post: @new_meal_post})) %>");
$(".new_meal_post").replaceWith('<%= escape_javascript(render(partial: 'meal_posts/new', locals: {new_meal_post: @next_meal_post})) %>');
flatpickr(".datetimepicker", {
altInput: true,
enableTime: true,
dateFormat: "Y-m-d H:i",
inline: true
});
5 changes: 5 additions & 0 deletions app/views/meal_posts/create_failure.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
console.log('You could not create your meal post.');
$(".meal_post_errors").remove();
errorMessages = '<div class="meal_post_errors"><% @new_meal_post.errors.full_messages.each do |message| %><li><%= message %></li><% end %></div><br>';
$(".new_meal_post").prepend(errorMessages);

2 changes: 1 addition & 1 deletion app/views/meal_posts/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- #TODO: POSTがないときはNO POSTくらい表示 -->
<h2><%= title %></h2>
<h3><%= title %></h3>
<div class="meal-posts">
<% meal_posts&.each do |meal_post| %>
<%= render partial: 'meal_posts/meal_post', locals: {meal_post: meal_post} %>
Expand Down
Loading

0 comments on commit 5df7742

Please sign in to comment.