使用 Mixin 和 ActiveSupport::Concern 重构Model
假设 Order 与 Reciept 这两个 model 里面都有 generate_token 这个 method。

app/models/order.rb
class Order < ActiveRecord::Base
  def generate_token
    self.token = SecureRandom.uuid
  end
end
app/models/reciept.rb
class Reciept < ActiveRecord::Base
  def generate_token
    self.token = SecureRandom.uuid
  end
end

重构代码,新增一个档案,将此method变成一个[特性]

touch app/models/concerns/tokenable.rb
app/models/concerns/tokenable.rb
module Tokenable
  def generate_token
    self.token = SecureRandom.uuid
  end
end

修改 Order 和 Reciept 代码

app/models/order.rb
class Order < ActiveRecord::Base
  include Tokenable
end
app/models/reciept.rb
class Reciept < ActiveRecord::Base
  include Tokenable
end  

但是发现原代码中还有一行跟generate_token有关的代码

before_create :generate_token

这2行代码是一组的,但是一般纯的 Ruby module 其实不认识 before_create,所以Rails内部开发一套工具
ActiveSupport::Concerns解决这个问题,让module也支援 before_create

app/models/concerns/tokenable.rb
module Tokenable
  extend ActiveSupport::Concern
  
  included do 
    before_create :generate_token 
  end
    
  def generatetoken
    self.token = SecureRandom.uuid
  end  
end

Controller 重构套路 1 - before_action

在一个常见的CRUD controller, 经常会出现一行类似下面的代码

@group = Group.find(params[:id])

经常出现在 show edit update destroy 里面
修改前代码

app/products_controller.rb
class GroupsController < ApplicationController

  def index
    @groups = Group.all
  end
  
  def new
    @group = Group.new
  end

  def show
    @group = Group.find(params[:id])
  end

  def edit
    @group = Group.find(params[:id])
  end

  def create
    @group = Group.new(group_params)

    if @group.save
      redirect_to groups_path
    else
      render :new
    end
  end

  def update
    @group = Group.find(params[:id])
    if @group.update(group_params)
      redirect_to groups_path, notice: 'Update Success'
    else
      render :edit
    end
  end

  def destroy
    @group = Group.find(params[:id])

    @group.destroy
    redirect_to groups_path, alert: 'Group deleted'
  end

  private

  def group_params
    params.require(:group).permit(:title, :description)
  end
end

修改方法,在private下面新定义一个函数find_group, 然后before_action 中声明哪些action可以使用这个函数

app/products_controller.rb
class GroupsController < ApplicationController

  before_aciton :find_group, only: [:show, :edit, :update, :destroy]
  
  def index
    @groups = Group.all
  end

  def new
    @group = Group.new
  end

  def show
  end

  def edit
  end

  def create
    @group = Group.new(group_params)

    if @group.save
      redirect_to groups_path
    else
      render :new
    end
  end

  def update
    if @group.update(group_params)
      redirect_to groups_path, notice: 'Update Success'
    else
      render :edit
    end
  end

  def destroy

    @group.destroy
    redirect_to groups_path, alert: 'Group deleted'
  end

  private

  def group_params
    params.require(:group).permit(:title, :description)
  end
  
  def find_group
    @group = Group.find(params[:id])
  end
end

使用before_action 的方法修改JD-store中的product,order,category controller

Controller 重构套路 2 - 继承Inherite

不同的controller都有同样的几个关于action的代码

app/controllers/admin/products_controller.rb
class Admin::ProductsController < ApplicationController
  before_action :authenticate_user!
  before_action :admin_required
  layout "admin"  
end
app/controllers/admin/orders_controller.rb
class Admin::OrdersController < ApplicationController
  before_action :authenticate_user!
  before_action :admin_required
  layout "admin"      
end

利用继承,新增一个AdminController

rails g controller admin 
app/controllers/admin_controller.rb
class AdminController < ApplicationController
  before_action :authenticate_user!
  before_action :admin_required

  layout "admin"

end

在其余的controller里面删除这些代码,采用 < 继承的方法,就是< AdminController
修改后的代码为

app/controllers/admin/products_controller.rb
class Admin::ProductsController < AdminController      # 继承AdminController 的特性

end
app/controllers/admin/orders_controller.rb
class Admin::OrdersController < AdminController        # 继承AdminController 的特性  

end

Bullet 是什么?

自动侦测 N+1 Query, Table Scan, Counter Cache的实用工具,
Github 地址:https://github.com/flyerhzm/bullet

安装

gem 'bullet' 必须添加在gem 'rails'gem 'mongoid'之后

Gemfile
gem 'bullet', group: 'development'

也可以直接放在group :development do里面

Gemfile
group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.

  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring

  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'

  gem 'bullet' 
end

配置configuration

Bullet是不会自己检测到错误的,必须配置。
config/environments/development.rb文件的最后一个end上方加入以下代码

config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.growl = true
  Bullet.xmpp = { :account  => 'bullets_account@jabber.org',
                  :password => 'bullets_password_for_jabber',
                  :receiver => 'your_account@jabber.org',
                  :show_online_status => true }
  Bullet.rails_logger = true
  Bullet.honeybadger = true
  Bullet.bugsnag = true
  Bullet.airbrake = true
  Bullet.rollbar = true
  Bullet.add_footer = true
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
end

这些功能其实不需要全部安装,只要选择几个需要的就可以了。如果全部安装,就会不停地报错

删除不需要的bullet, 只安装以下的功能,rails s就不会报错

config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
  Bullet.add_footer = true
end

使用效果

遇到页面中有 N+1 Query 的问题,就会有弹窗提示。我用rails101专案进行测试。

根据提示,找到bug的位置

修改代码如下

groups_controller.rb
-    def index
-        @groups = Group.all             
-    end

+    def index
+        @groups = Group.includes(:user).all
+    end   

再执行rails s,不会出现弹窗
再打开一个讨论组,又出现弹窗报错

找到bug的位置

修改代码

groups_controller.rb
- def show
-   @group = Group.find(params[:id])    
-   @posts = @group.posts.recent.paginate(:page => params[:page], :per_page => 5)
- end

+ def show
+   @group = Group.find(params[:id])  
+   @posts = @group.posts.includes(:user).recent.paginate(:page => params[:page], :per_page =>   5)
+ end   

没有修改groups_controller.rb代码前,cache次数很多


log/bullet.log 保存报错记录

session 出现在教材https://fullstack.xinshengdaxue.com/posts/416
加入购物车代码

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

 # ...略


+  helper_method :current_cart

+  def current_cart
+    @current_cart ||= find_cart
+  end

+  private

+  def find_cart
+    cart = Cart.find_by(id: session[:cart_id])
+    if cart.blank?
+      cart = Cart.create
+    end
+    session[:cart_id] = cart.id
+    return cart
+  end
end

session是用户ID的一个载体。在购物商店的例子中,每个人进入商店的时候,都会领到一张电子卡就是session。只要查询session上的card_id就可以这台购物车里面有多少商品和每种商品的数量。 如果发现你的车丢了,if cart.blank? = true,会通过cart=Cart.create重新发一辆车并且分配一个新的购物车号码 session[:cart_id]=cart.id.每个用户当前的购物车current_cart就是通过这样的方式存取的

session的工作原理

当程序需要为某个客户端application的请求request建立一个 session 的时候,服务器首先检查这个客户端的请求中是否已经包含了一個 session辨识码 - 称为 session id。如果已经包含一个 session id,则说明以前已经为此客户端创建过 session,服务器就按照session id 把这个 session 检索出來使用。如果客户端请求不包含 session id,则为此客户端创建一個 session 并且生成一个与此 session 相关联的 session id。session id 的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串。这个 session id 将在本次响应respone中返回給客戶端保存。保存这个 session id 的方式可以采用 cookie,这样在交互过程中浏览器可以自动地按照规则把这个标识发給服务器。

session 存在的原因

session 之所以会存在,是因为 HTTP 为 stateless无状态的设计,Server 和 Client 不会一直保持连线状态,也不会有双方状态的即时更新。所以,Server 并不知道 Client 的状态(像是否已经登入)。因此,后来的网站开发者,采用 Session这样的设计來解決这个问题。

card_id的储存需要载体,所以代码不能写成cart = Cart.find_by(id: cart.id), 要写成

session[:card_id] = cart_id
cart = Cart.find_by(id: session[:cart_id])

Rails,session 默认存储于 cookie 中,可通过 config/initializers/session_store.rb 查看。

session 以 cookie 的方式存储的加密还是会依赖于 secret_key_base 参数的值,所以 secret_key_base 千万不能泄漏。

指定:session[:cart_id] = cart.id
读取:session[:cart_id]
刪除:session[:cart_id] = nil
清空:reset_session

参考资料:
https://en.wikipedia.org/wiki/Session_computer_science)(
https://rocodev.gitbooks.io/rails-102/content/chapter2-rails/cookies-and-session.html
https://ihower.tw/rails/actioncontroller.html#sec8
https://en.wikipedia.org/wiki/Session_ID
http://www.tuicool.com/articles/f6F7by
http://sylviablog.logdown.com/posts/1786808

作为一个没有参加第一期2次魔改大赛的学员,在参加第二期2次魔改大赛之后,终于切身感受地自己不参赛失去了什么和如果当时参赛,现在的自己对编程肯定不会如当时那么害怕,也会对自己学会编程有信心。

第一期没有参赛的原因分析

懒得动脑子 懒得动脑子 懒得动脑子 因为懒得动脑子,所以不去找能做出作品的方法,google就在手边,就是懒得输入”how to make a landing page?" "how to use google chrome to copy a website ?"也不去论坛上看同学们总结的办法,只是不停地给自己灌输一个观念 “魔改太难了,我无从下手,我不会,我不想做“,“为什么Xidte老师不出一个魔改教程?”
我也没有向Xdite老师和助教请教解决办法,就放纵自己这么懒下去,错过了2次改变自己的机会。 “懒得动脑子” 带来地严重后果是竟然真地觉得自己什么都学不会了 然后就自暴自弃了。直到第一期魔改大赛结束,看了很多做出优秀作品的同学的心得才发现他们都是靠着勤奋地练习,不停地找方法,最终才做出完整的作品。而我更像是一个伸手党,如果手边没有那么多现成地教程没人手把手教学,就放弃不学了。现在想想,这是我没有参赛的最大收获,我认清了自己对待每一个看似艰难的问题的处理方法,就是逃避找寻解决问题的方法,直到把问题变成难题,然后对自己能力的信心就开始递减 这不仅仅是一次没有参加魔改大赛的失败,更是我以往学不会其他技能的原因,我总是幻想自己什么都会,遇到问题,如果用当前的知识不能解决,我就放弃了,甚至都不去思考如何才能解决问题,最后只能放弃。如果我不能改变自己这种懒惰地,甚至是愚蠢的思考方式,那我以后真的是什么都学不会了。不听话 没有老实地按照Xdite老师的话写ORID,多练习,又为自己的自以为是买单了。

第二期魔改大赛的心得体会

  1. 参与就会变地不一样 还记得Nic助教在直播时说的话“世界上有百万富翁,难道你就不去挣钱了嘛?”“做不到那些优秀同学的程度,就要放弃自己的成长了嘛” 这一次我选择了参赛,刚开始的时候,又陷入了上一期的循环,发现自己什么都不会,最后想到了一个解决问题的办法:一个字 根据我现在掌握的知识,我还不能去“创造”什么,那么就抄起来!已经有了第一期那么多同学的作品,看到自己喜欢的就去他们的github找对应的css,html。抄的过程也是一种学习。会去想办法从同学的commit中找到实现一种功能的代码是按照什么顺序写出来的。不懂地地方就重新回到教程,看每一个model, controller建立的过程,虽然不知道具体什么,当时只是记得输入什么,输出什么。就这样一步一步做出需要的功能。

  2. 耐心 耐心 耐心 遇到问题,着急有什么用?没用,只不过当伸手党久了,总是幻想能立马搜索到想要的答案,或者教程,不动脑子地抄下来就好。找不到答案,就自动放弃,然后抱怨自己怎么这么笨。这种浮躁的心态只会越来越严重。上次失败的教训让我时刻警戒自己:耐心,再坚持一会,再看看问题,想想哪里出错了。在处理一次后端代码报错时,我固执地不问助教,自己尝试解决,后来比对类似功能的正确代码,发现只是变量前多了一个@造成的,去掉之后bug就解决了。虽然背后的原理我当时还是不懂,但是我总算学会了一次靠自己解决问题,发现问题其实并不难,难的是按耐自己浮躁的心态,冷静地学会分析处理问题。

  3. 给自己100%的信心 信心不是别人给的,要100%相信自己能成长。做的没有同学好,难道就不做了?这样的比较只会觉得自己一无是处。跟别人比较,要把重点放在自己未来还有很多成长空间,要向优秀的同学学习。跟自己比较才能加速自己的成长,不停地发现自己的弱点,不停地正向迭代,才能走出自己思维的坏循环。

  4. 不害怕出错才能进步老实地讲,这次的功能都是抄同学总结好的教程。自己找的答案,总是怕抄出来出错,出错又不知道怎么解决,就在论坛上找完整的教程。怕出错,也是一种懒惰的心态,Xdite老师在直播说,出错也是一种学习,才能明白代码为什么要这样写。可是我怕出错,怕麻烦,怕花了时间,没得到想要的结果。以后要改变心态,敢于试错,错了就学着去找答案,不能懒。

  5. 练习 练习 练习 Xdite 在直播时讲到,“自信,变强,都是靠不断地练习,压缩每一次做出功能的时间。成长等于下一次作同样的事情,更快,更像天才。天才不是天生的,要靠不断地练习把自己变成天才”。所以要像变成天才,就是多练,多复盘。

在本地修改CSS之后,部署到heroku没变化,
1 执行预先编译的操作

rake assets:precompile  ##compile all the assets named in config.assets.precompile
git add .
git commit -m"precompile"

2 修改config/environments/production.rb

config.assets.compile = false  => config.assets.compile = true 
rake assets:precompile
git add .
git commit -m"precompile" 
heroku logs|grep -i -E'error|fatal'
heroku logs|grep -i error
bundle exec rake assets:precomplie
git push -f heroku master
rake db:clean
rake assets:clean
rake assets:precompile

检查错误

heroku logs|grep -i error 

强推到heroku

git push -f heroku master

事先编译

bundle exec rake assets:precompile

Order.new         (O) 新增一个Order的物件
Order.build       (X) 没有这种写法

在rails 源码里面new 和 build 是 alias (别名)

current_user.orders.new      (O)
current_user.orders.build    (O)

以新建订单为例,教材给出的写法是

def create
  @order = Order.new
  @order.user = current_user
end

也可以这样写

def create
  @order = current_user.orders.build
end

# ! 放前面

!current_user   如果current_user 不存在
此时 ! = NOT

# != 放在一起

current_user != user       不等于

# !放在后面

Ruby     表示执行此method会变更自己的状态
# controller 

def hide 
    @order.hide!
end 

# model 

def hide!
  self.hidden = true               hide 栏位本来是false 会变成true
  self.save 
end 

ActiveRecord 内 xxxx! 表示执行此 method 有可能 raise error (ActiveRecord就是rails用的库)

User.create => false             create 没有成功,就会是false 
User.create!  => ActiveRecord::RecordInvalid   

xxx?

Ruby 内
xxx! 表示执行此 method 本身状态会被改变

xxx? 表示执行此 method 回传值是 boolean (true/false)