BEGINNER ~ INTERMEDIATE
Suitable for Rails beginners with around 3 months of experience
A Bit of JavaScript
BBS/PTT ASCII art
老生聽到膩的老生常談
2015.rubyconf.tw
FTW CONTENT RATING
Github: lulalala
Twitter: lulalala_it
likes anime, manga, games and drawing
I was not motivated to practice any more
(●;-_-)●
So I wrote an imageboard
http://board.lulalala.com/cat
(the guys who made Docker)
and then...
梅
and I forgot about mei...
I got a REAL job (  ̄ c ̄)y▂ξ
Let's start!
topics
id |
title bumped_at |
posts
id topic_id(fk) |
content |
has_many
Topic
Post
Post
Post
1. Multi-tenancy
2. Form Object
3. Use Cells to replace Partial
4. Avoid Form Re-render
多住戶架構
So we might as well host more boards using one Ruby process
# I use SettingsLogic
def config
@cascaded_config ||= AppSetting.board.dup.tap do |c|
if board_config = super
c.merge!(board_config)
end
end
end
Have a config :text column
serialize :config
And then merge over default settings
# Global setting
board:
pagination:
per_page: 5
max_page: 10
# Board setting
# in config column
pagination:
per_page: 3
@board = Board.find(52)
puts @board.config.pagination.per_page
#> 3
puts @board.config.pagination.max_page
#> 10
moral of the rewrite #1
TopicController#create
(for creating new topic)
PostController#create
(for replying)
<%= form_for(@topic, format:'json', remote:true) do |f| %>
<%= f.hidden_field :board_id %>
<%= f.fields_for :posts, @post do |pf| %>
<div class="field">
<%= pf.label :author %>
<%= pf.text_field :author %>
</div>
<div class="field">
<%= pf.label :email %>
<%= pf.text_field :email %>
</div>
<%= render_cell :post, :image_upload_control, pf %>
<%= pf.label :content %>
<%= pf.text_area :content %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<%= form_for(@post, format:'json', remote:true) do |f| %>
<%= f.hidden_field :topic_id %>
<div class="field">
<%= f.label :author %>
<%= f.text_field :author %>
</div>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<%= render_cell :post, :image_upload_control, f %>
<%= f.label :content %>
<%= f.text_area :content %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
TWO SETS OF _form.html.erb
TWO SETS OF create action
TWO SETS OF strong parameter
TWO SETS OF CSS selector
TWO SETS OF error handling
TWO SETS OF JS DOM manipulation
TWO SETS OF tests
kekeke
New Topic
Reply
○(#‵︿′ㄨ)○ that's it
Combine them will save brain-power,
making it easier to reuse code,
less potential bugs
清爽
所以每次看到這些重複的時候就十分讓我不爽
class PostForm
include ActiveModel::Model
attr_accessor :topic, :post
def attributes(params)
if new_topic?
@topic.assign_attributes(topic_params(params))
end
@post.assign_attributes(post_params(params))
@post.images << build_images_from_params(params)
end
delegate :title,
:board_id, to: :topic
delegate :author,
:content,
:email,
:topic_id,
:images, to: :post
def valid?
validity = true
errors.clear
[post, topic].each do |object|
if !object.valid?
validity = false
object.errors.each do |key, values|
errors[key] = values
end
end
end
validity
end
def save
return false if !valid?
ActiveRecord::Base.transaction do
@post.save! if @post.changed?
@topic.save! if new_topic?
end
true
rescue ActiveRecord::RecordInvalid => invalid
false
end
Both forms are now from the same erb view,
and points to same PostController#create \⊙▽⊙/
ALL ROADS LEAD TO ROME
Framework's Way can't always help you.
morale of the rewrite #2
def show
@books = [
"三体",
"Spin",
"アイの物語",
]
end
<% @books.each do |b| %>
<%= b %>
<% end %>
def show
end
<% @books.each do |b| %>
<%= b %>
<% end %>
ERROR
if you forget to prepare it,
view will error out
<%= render partial: "ad",
locals: {var: var} %>
_ad.html.erb
AD1
<%= var.get_ad() %>
<%= render_cell :ad, :show, var %>
app/cells/ad/show.html.erb
AD1
<%= @var.get_ad() %>
class AdCell < Cell::Rails
def show(var)
@var = var
render
end
end
準備資料
show.html.erb
<% @text = "el psy congroo" %>
<%= render partial: :foo %>
partial
<%= @text %>
show.html.erb
<% @text = "el psy congroo" %>
<%= render_cell :foo, :bar, @text %>
partial
<%= @text %>
No @var contamination
封裝
partial
<% beers = if foo?
Beer.where("name like ?", b)
elsif bar?
Beer.where("name like ?", c)
end
%>
<% beers.each do |b| %>
<%= b.name %>
<% end %>
cell
def show
@beers = if foo?
Beer.where("name like ?", b)
elsif bar?
Beer.where("name like ?", c)
end
render
end
<% @beers.each do |b| %>
<%= b.name %>
<% end %>
partial
<% cache("#{@foo}-#{@foo.replies.last.updated_at}", expires_in: 60.seconds) do %>
<%= @foo.title %><br />
<%= @foo.body %><br />
<% end>
cell
def show(foo)
@foo = foo
render
end
cache :show, expires_in: 60.seconds do |cell, foo|
[@foo, @foo.replies.last.updated_at]
end
cell view
<%= @foo.title %><br />
<%= @foo.body %><br />
very long cache key logic
helper
module FooHelper
def foo_body
# if only used in foo partial
end
end
cell
def show(foo)
@foo = foo
render
end
private
def body
end
cell view
<%= body %>
partial
<%= foo_body %>
For now I recommend cells version 3.11
instead of 4.x
sorry nick don't hit me
Last change:
if you forget to prepare it,
view will error out
To re-iterate
def update
@book = Book.find(params[:id])
if @book.update(book_params)
redirect_to @book, notice: 'Good!'
else
render :edit
end
end
def edit
@book = Book.find(params[:id])
@obscure_var = "52" # which is easy to forget
end
remote: true
form_for(@post_form) do |f|
form_for(@post_form,
remote:true,
format:'json',
data:{type: :json}) do |f|
remotipart gem
Many iterations until I got out of the pitfall
now what?
Part III
$boardQuery = prepare("SELECT COUNT(1) AS
'boards_total', SUM(indexed) AS 'boards_public', SUM(posts_total) AS 'posts_total' FROM ``boards``");
$boardQuery->execute() or error(db_error($tagQuery));
$boardResult = $boardQuery->fetchAll(PDO::FETCH_ASSOC)[0];
$boards_hidden = number_format( $boardResult['boards_total'] - $boardResult['boards_public'], 0 );
$boards_omitted = (int) $searchJson['omitted'];
$posts_total = number_format( $boardResult['posts_total'], 0 );
I was reminded that why I started learning Rails 3 years ago
1. Ruby aims to make programmers happy
2. Rails framework guides and help me to make decisions
and FAQ time
thank you
﹨(╯▽╰)∕