新gem bullet功能与使用

Published on:

通常都會用 Bullet 來檢查 N + 1 Query 問題(對資料庫額外執行多個查詢子句而影響效能)

Bullet 會在執行查詢的時候,將發生 N + 1 Query 的部分輸出於 log 上

修改專案中的 Gemfile 透過 bundle 安裝

group :test, :development do
  gem 'bullet'
end

將 Bullet 加入到 development 與 test 群組以看到 Bullet 所產生的 log

修改 config/environments/test.rb,加入以下設定

config.after_initialize do
  Bullet.enable = true
  Bullet.bullet_logger = true
  Bullet.raise = true # raise an error if n+1 query occurs


end

並在 spec/spec_helper.rb 或 spec/rails_helper.rb 中加入以下設定

RSpec.configure do |config|

  # other configures...


  
  if Bullet.enable?
    config.before(:each) do
      Bullet.start_request
    end

    config.after(:each) do
      Bullet.perform_out_of_channel_notifications if Bullet.notification?
      Bullet.end_request
    end
  end
end

這樣在跑測試的時候,如果有 N + 1 Query 時就會無法通過測試

再依照出現的訊息去改善程式吧

1.为什么要将 CSS 放在最顶层,将 JavaScript 放在最底层?

Published on:

css 放头部主要是为了避免最后加载Css引起的浏览器白屏,改善用户体验。
而js放底部是因为浏览器渲染HTML文件是从上往下渲染的。先执行head标签,再执行body标签,一行行渲染下去。无论当前script代码是内嵌还是在外链文件中,页面的下载和渲染都必须停下来等待脚本执行完成。script执行过程耗时越久,浏览器等待响应用户输入的时间久越长。所以js尽量放底部可以有一定的性能优化效果。

2.如何尽可能减少HTTP Requests?

一个网页不可避免的要引入大量的外部文件:JavaScript、css、背景图片……由于Http协议的无状态性,用户的每一次访问,都会重新向服务器请求所有文件,而大量Http请求的累加,正是影响网站速度的最主要原因。

所以这里的解决方法只有一个:合并。最理想的情况莫过于一个网页只包含一个css,一个js,一张背景图。

合并Js和Css文件很好理解,背景图片要怎么合并?这里采用的主要方法是CSS Sprites,简单说就是把所有的图片拼接成一张大图,在不同的Css里指定背景图坐标来显示不同图片。具体可以参考Dave Shea的Image Slicing’s Kiss of Death 一文,还有网站提供了在线的CSS Sprites服务 ,只需要上传小图片,就可以获得拼接后的大图以及相应坐标。

不过在当前越来越多动辄包含10余个文件的开发框架面前,减少Http请求数也变得越来越难。一直都认为所谓框架,给出的应该是一整套完善的开发思 想,从服务器配置到数据库设计甚至是到UI体验乃至SEO,但现在很多Framework总是各自为战,后台与前端脱节,只在自己的一片领域里提供一定程 度上的方便,没有考虑到最终产品的统合,甚至连基本的代码侵入性问题没有处理好(这里点名批评dojo,恨不得在所有的html标签上印上dojo的章 子),不能不说是一种遗憾。

所以如果网站中采用框架的话,在框架的选择面前,建议多采用轻量级,侵入性低的框架,也是为了日后产品的优化维护着想。

3.CDN是什么?请用自己的话解释

CDN(Content delivery network)内容分发网络,能够智能根据网络节点情况选择服务节点,大型网站部署时尤为重要。
自己的话解释就是离你最近速度最快的云服务器

4.如何提升自己的网站加载速度?

使用良好的结构
可扩展 HTML (XHTML) 具有许多优势,但是其缺点也很明显。XHTML 可能使您的页面更加符合标准,但是它大量使用标记(强制性的 标记),这意味着浏览器要下载更多代码。
所以,事情都有两面性,尝试在您的网页中使用较少的XHTML代码,以减小页面大小。
如果您确实不得不使用XHTML,试着尽可能对它进行优化。

不要使用图像来表示文本
使用图像表示文本的最常见示例就是在导航栏中,美观的按钮更加具有吸引力,但是它们的加载速度很慢。
此外,图像仍然不能由搜索引擎直接索引,因此,使用图像进行导航不利于搜索引擎优化,当无需图像就可以通过大量 CSS 技巧创建漂亮的按钮时,绝不使用图像来表示文本。

不要包含不必要的JavaScript代码,尽可能将其外部化
应该明智地使用JavaScript(仅在真正必要时才使用)并优化脚本的大小和速度,缩短JavaScript下载时间的另一种方式是使用外部文件,而不是包含脚本内联,这种方法也适用于CSS,因为浏览器会缓存外部化的文本,而(在HTML页面自身中)以内联方式编码的 CSS 或 JavaScript 每次都会随 HTML 一起加载。

设置图像大小
与表格单元格、行和列一样,当您未明确设置图像大小时,浏览器需要执行计算来显示图像,这会降低处理速度。
你会给每个图片加上height和width属性吗?这两个属性可以让浏览器在加载图片之前就知道图片的长和宽,并预留出指定的长宽待图片加载后显示,如果没有这两个属性,浏览器还需要在读取图片成功后再处理一次页面布局样式,这无疑减慢了网页加载速度,所以在固定图片大小的情况下最好都使用上长和宽属性。

按需加载 JavaScript 文件
要按需加载 JavaScript,使用 import() 函数。
function $import(src){
var scriptElem = document.createElement('script');
scriptElem.setAttribute('src',src);
scriptElem.setAttribute('type','text/javascript');
document.getElementsByTagName('head')[0].appendChild(scriptElem);
}
// import with a random query parameter to avoid caching
function $importNoCache(src){
var ms = new Date().getTime().toString();
var seed = "?" + ms;
$import(src + seed);
}

使用内容分布网络
内容分布网络(Content-distribution network,CDN)是另一种缩短下载时间的好方法,当您将静态图像放在 Internet 上的许多服务器上时,用户能够从离他们最近的服务器下载这些图像。
此外,大多数 CDN 都在快速服务器上运行,因此无论服务器的加载速度如何,其响应速度都比小型的超载服务器快。

使用 PNG 格式的图像
Graphic Interchange Format (GIF) 和 Joint Photographic Experts Group (JPEG) 图像格式都已过时了:Portable Network Graphic (PNG) 是未来流行的格式。当然,您可以说 GIF 和 JPEG 已经消亡,或者 PNG 没有任何缺陷,但是所有事物都有各自的优缺点,PNG以最佳的文件大小提供了出色的质量,因此,如果进行选择的话,应该尽可能使用 PNG 图像

使用内容分发网络CDN
服务器在处理大流量的数据是十分困难的,这最终导致页面加载速度变慢。CDN是位于全球不同地方的高性能网络服务,它会复制你网站的静态资源,并以最有效的方式来为访客服务,使用CDN,可以提升页面的加载速度。

把CSS文件放在页面头部,JS文件放在底部
把CSS文件放在头部可以禁止逐步渲染,节省浏览器加载和重绘页面元素的资源,把JS文件放在页面底部可以避免代码执行前的等待时间,从而提升页面的加载速度。

压缩整合CSS、JS文件减少HTTP请求次数
压缩会移除一些不必要的字符,从而帮助减少文件大小和网页后续的加载时间。
现在的网页都有多个图片、CSS外部文件链接、Javascript外部脚本链接,所以当访问一个网页时浏览器需要多次向服务器请求这些文件,在请求和加载之间会产生不少的时间差,特别是一些网页上有多个小图片、图标按钮的网页,有多少图片,浏览器就需要请求多少将这些小文件,多将请求这些小图片文件将明显影响网页的加载速度。
所以,我们应该尽可能将小图片拼合一个PNG大图片上,然后通过坐标来显示图标,一次请求一个大图片比多次请求小图片速度要快不少。
同样,最好将CSS和Javascript尽可能地整合到一个文件中都有助于加快网页载入速度。

个人与小微企业网站的4大重要元素

Published on:

网站的运载速度

不说你也许不知道,不少用户对网站的加载速度只有 2 秒,一旦超过 2 秒他们将可能失去兴趣,还需要花上一分钟来打开的网站就更不用说了。倘若你在设计网站时已尽量地优化了你的网站内容,那么打开企业的网站理应该十分迅速,若你企业的网站加载速度依然很慢,那么你则需要考虑问题是否出在你的伺服器上,那么你可以怎样避免这种情况呢?当然就是在选择伺服器前,你需要去了解你伺服器供应商的用户评论,以及测试他们伺服器的速度。

上线时间

基本上每个伺服器供应商都会说自己可达到 99% 以上,那么上线时间为何又那么重要呢?因为上线时间也就保证一年365 天你的伺服器有多久保持正常运行,若你的伺服器总宕机,可想而知你的用户也就打不开你的网站了,如总打不开你的网站,那结果当然是这些用户不会再访问你的网站,这不仅仅是客户流失问题,还直接影响你的销售业绩。

有不少原因会造成伺服器上线时间低下,如伺服器供应商的设备不够好,人员操作的失误,防火墙受到黑客攻击等,虽然没有任何一个伺服器可以保证100% 的上线时间,但你还是需要找一个最接近的,如我们VPShosting 就达到99.9% 的上线时间。

即时售后服务

试想想若你所选的伺服器宕机了,可是当你发现你网站打不开时,你无法找到即时的售后服务,那你的问题将无法迅速解决,倘若你的伺服器供应商连电话也找不到人,那么你损失的将不只是访问流量,而是你的消费者。

安全隐患

你需要知道黑客真的是不眠不夜地在伺机而动,若你伺服器不够安全还在用一些旧的系统,那么这将大大地增加你网站被攻击的风险,因此你在选择伺服器时也需要了解清楚你所选伺服器的程序是否最新版本,以及他们到底有什么保护机制。

实现评论功能

Published on:

step1 :rails g model review product_id:integer user_id:integer body:text

rake db:migrate

step2: 修改model

user.rb / product.rb :
has_many :reviews

review.rb
belongs_to :user
belongs_to :product
validates :body, presence: true

step3: 增加routes

resources :products do
resources :reviews
end

step4: rails g controller reviews

class ReviewsController < ApplicationController
  before_action :authenticate_user!, only: [:create, :destroy]
  def create
    @product = Product.find(params[:product_id])
    @review = @product.reviews.new(review_params)
    @review.user = current_user

   if @review.save
  redirect_to product_path(@product), notice: 'Review was successfully created.'
else
  redirect_to product_path(@product), notice: 'You have to write some words.'
end
  end

  def destroy
    @product = Product.find(params[:product_id])
    @review = Review.find(params[:id])
    @review.destroy
    redirect_to product_path(@product), alert: "You have deleted the review successfully"
  end

  private
  def review_params
    params.require(:review).permit(:body)
  end
end

step5 : 产品 show 页面添加:

 <div class="row review">
  <div class="col-sm-10 col-sm-offset-1">
    <h3 class="reviews_title">
      <%= @product.reviews.count %> Reviews
    </h3>
    <hr>
    <div id="reivews">
      <%= render @product.reviews%>
      <hr>
      <%= render "reviews/form"%>
    </div>
  </div>
</div>

step6. touch app/views/reviews/_review.html.erb

 <div class="reviews_wrapper clearfix">
    <div class="pull-left">
        <p class="lead"><%= review.body %></p>
        <p>
            <small>Submitted
                <strong><%= time_ago_in_words(review.created_at) %>
                    ago</strong>
                by
                <%= review.user.email %></small>
        </p>
    </div>
    <div>
<% if review.user == current_user %>
   <%= link_to("Delete", product_review_path(@product, review), method: :delete, data: { confirm: "Are you sure?"}, class: "btn btn-sm pull-right") %>
<% end %>
  </div>
</div>

step7 . touch app/views/reviews/_form.html.erb

<%= simple_form_for([@product, @product.reviews.build]) do |f|%>

  <div class="field">
      <%= f.text_area :body, class: "form-control" %>
  </div>


  <%= f.submit "Add a Review", class: "btn btn-primary" %>
<% end %>

站内信

Published on:

Ruby on Rails 實戰聖經 - ActionMailer - E-mail 發送

建立一個Mailer寄信程式

和Controller一樣,Rails也用generate指令產生Mailer類別,此類別中的一個方法就對應一個Email樣板。以下是一個產生Mailer的範例:

rails generate mailer UserMailer notify_comment
如此便會產生app/mailers/user_mailer.rb檔案,並包含一個notify_comment的Action,其template在app/views/user_mailer/notify_comment.text.erb(純文字格式)和notify_comment.html.erb(HTML格式)。如果兩種格式的樣板檔案都有,那麼Rails會合併成一封Multiple Content Types的Email。

讓我們看看 user_mailer.rb 的程式:

class UserMailer < ActionMailer::Base
default :from => "寄件人名字 noreply@example.org"

def notify_comment(user, comment)
    @comment = comment
    mail(:to => user.email, :subject => "New Comment")
end

end
其中default方法可以設定預設的寄件人。而 mail 方法可以設定收件人和郵件主旨。和View一樣,@user物件變數可以在app/views/user_mailer/notify_comment.text.erb或app/views/user_mailer/notify_comment.html.erb或樣板中存取到。而mail方法則還可以接受其他參數包括cc、bcc。

我們可以在rails console中測試,執行UserMailer.notify_comment(user, comment).deliver_now!就會寄信出去。(這裡我們假設存在一個user和comment物件代表使用者和新留言,例如user = User.first和comment = Comment.last)

實務上,我們會在controller之中,例如使用者張貼留言之後寄發信件:

def create
comment = Comment.new(comment_params)
if comment.save
UserMailer.notify_comment(current_user, comment).deliver_later!
redirect_to comments_path
else
render :action => :new
end
end

如果只需要純文字版,就砍掉app/views/user_mailer/notify_comment.html.erb這個檔案,然後在app/views/user_mailer/notify_comment.text.erb純文字格式中,可以加入以下文字跟網址:

有新留言在 <%= comments_url %>
另外,因為寄信這個動作比較耗時,通常我們也會搭配使用非同步的機制,因此上述用法分成了deliver_now!和deliver_later!兩種,而後者就會搭配ActiveJob進行非同步的寄送,我們在非同步一章會詳細介紹如何設定。

Helper 的使用

在 email 樣本中,預設是不會載入 app/helpers 裡面的 Helper 方法的,如果你要使用的話,可以在該 Mailer 類別中宣告如下:

class UserMailer < ApplicationMailer
helper :application # 這樣會載入 app/helpers/application_helper.rb
helper :users # 這樣會載入 app/helpers/users_helper.rb
# ...
end
開發預覽
開發期間我們需要常常測試預覽寄出的Email內容,但是實際寄送出去又很沒效率。我們可以安裝letter_opener這個gem,修改Gemfile加入:

gem "letter_opener", :group => :development
然後將config.action_mailer.delivery_method改成:letter_opener

這樣在開發模式下,就會開瀏覽器進行預覽,而不會真的寄信出去。

第三方寄信服務

由於Gmail是個人用途使用,用量有限,並不適合開站做生意使用。我們實務上我們會使用第三方服務來確保Email遞送的可靠性,例如:

mailgun
SendGrid
Postmark
MailChimp
AWS SES
大量寄送 Email 會是一門學問,請參考 如何正確發送(大量) Email 信件 這篇文章
Email CSS 處理
Email 會被各種奇形怪狀的閱讀器所瀏覽,這些環境中的 CSS 支援非常受限:
https://blog.othree.net/log/2016/08/25/modern-html-email-develop/
https://www.campaignmonitor.com/css/
http://templates.mailchimp.com/development/css/
解法: 需要將 CSS inline 內嵌到 HTML 裡面
安裝 https://github.com/fphilipe/premailer-rails
我們也可以套現成的 Email Responsive Template 樣板,例如:

http://blog.mailgun.com/transactional-html-email-templates/
https://github.com/leemunroe/responsive-html-email-template
https://htmlemail.io/ $49
收信
Active Mailer也可以辦到收信,但是你需要自行架設郵件伺服器。因此需要這個功能的話,也會使用第三方服務,例如mailgun和MailChimp都有提供收信的Webhook服務:信寄到第三方服務,然後第三方再呼叫網站的HTTP API,這樣就省去你自己架設郵件伺服器的困難。

周记小结

Published on:

完成了购物商城3次,购物车设计和订单生成以及乱码加入确实挺难的,容易搞不清楚逻辑。只字不差的阅读教材,把自己定义成新手重复练习吧!

先说说本周最大的坑

第2遍的时候 rails g model order 去修改参数手残弄错执行了rake db:migrate 以后很多步都没发现错误,后来报错检查的时候发现是数据库出错,而我又忘了做多少步了。就只能rake db:drop了。惨的要死,因为我当时直接的反应是 git add .
git commit -m "手残弄错数据"然后git checkout stor4git branch -D story5。结果这样不行,是数据库出错。又来了一遍.... rake db:drop rails g model order rake db:migrate 好了。就当重复练习吧。

再说说本周最大的收获

我找了很多手册,html和css的手册。手册相当于新华字典。等你以后要用到的时候,可以查到用到.包括rails的手册都可以。

blank?是什么

Published on:

Rails中会常用到的blank? 方法
blank?方法是Rails而非Ruby中的方法. 基本上它实现了nil? empty?的作用. 下面是来自Rails API文档中的介绍:
“An object is blank if it‘s false, empty, or a whitespace string.

For example, "", " ", nil, [], and {} are blank.”
举例来说, 我经常会在模型中赋值的场景调用blank?方法. 回想之前的例子-- dog哈希表, 我们可以这样来简化检验值

[ruby] view plain copy
script/console >>

dog = {:name => "Beauregard"}
puts "What kind?" if dog[:breed].blank?
=> What kind?

dog = {:name => "Beauregard", :breed => ""}
puts "What kind?" if dog[:breed].blank?
=> What kind?

可以看到 .blank? 的结果 相当于 .ni? + .empty?

routes 中的collection 和 member

Published on:

member,new,collection的区别:

:member 是对单个实体进行操作,创建路由格式是: /:controller/:id/:your_method
:collection 是对实体集合进行操作,创建路由格式是: /:controller/:your_method
:new 是新建一个实体,创建路由格式是: /:controller/:your_method/new

举例如下:

map.resources :users, :collection => { :rss => :get }
map.resources :users, :member => { :profile => :get }
map.resources :users, :new => { :draft => :get }
第一行创建的路由是:/users/rss
第二行创建的路由是:/users/1/profile
“1”就是user_id,我们需要知道用户ID才能得到用户的profile.
第三行创建的路由是:/users/new/draft

helper_method是什么意思

Published on:

Helper是什麼

Helper是一種輔助方法,目的是將Ruby code帶入view當中,而不是單純的平舖直敘。這點也是Rails分工的一環,model和controller的method不能在view當中直接使用,只有helper method才行。

Helper和Helper method差在哪裡?

Helper是一個概念,而在Rails架構中helper自己有一個資料夾,我們可以在裡面定義helper method,定義完以後就可以在view當中使用。Rails當中有提供很多內建的helper,只要是.erb結尾的檔案,都可以使用helper,包括html.erb、js.erb等等。

當然,helper這個詞並不會使用於controller和model當中。

find与find_by的区别

Published on:

find

根据id进行查询,像Product.find(3),查询语句是Product Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]],也可以直接传一个Product的对象,像

product = Product.first
Product.find(product)

find会把传过去的model对象的id进行查询。甚至可以这样:

user = User.last
Product.find(user)

find会把user的id的值进行查询。
find没有查询到结果,会抛出一个ActiveRecord::RecordNotFound异常。

find_by

需要传递一个hash作为参数。像Product.find_by(id:3),查询语句是Product Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]。当然也可以查询其它的字段,像Product.find_by(title: 'the yellow book',查询语句是Product Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."title" = ? LIMIT ? [["title", "the yellow book"], ["LIMIT", 1]]
find_by没有查询到结果,会返回nil

where

where返回的是一个ActiveRecord_Relation集合,并不是一个model的对象,像product = Product.where("id = 1"),查询后,这样使用product.id是不行的,需要这样prodcut.first.id或者product.take.id