现在休息是为了走得更远,楼主加油!
@billy :thumbsup: 这个图示等于把迭代前的版本完全推倒重来了,开发成本可不小啊,客户不一定接受得了:)
用网盘啊
非常好的经验分享,干货满满的!
这种办法是目前我们能想到的最佳解决方案,不知道有没有更好的处理方式。
cache_key 的思路是对的,但只是应该放到一个 helper 里实现而不是用一个 model concern,因为
这个更多是一个 view 层面 render 时要顾虑的东西
把所有 cache key 定义同一个文件中,可以有个概览视图,可以知道到底有哪些 cache 是相关的,
有时对同一个 collection, 在某些页面因为包含了其他过期数据需要过期掉(比如本文中的 projet.name),而其他地方不需要过期,两者用同一个 cache_key 就会有“错杀”
更容易加入一些全局性的变化因子 (比如:i18n) 而不用改每个 view 的代码
代码思路如下:
module CacheHelper
def cache_key_for(resource, key = nil, *changes)
cache_key = case key
when :todolists
[resource.max(&:updated_at)]
when :todolists_with_project
[resource.max(&:updated_at), @project.name]
# ... define more keys
else # default will use updated_at
if resource.respond_to?(:to_a) # resource is a collection
resource.map(&:updated_at)
else
[resource]
end
end
# final keys
[I18n.locale].concact(cache_key).concat(changes)
end
end
用法:
<% cache cache_key_for(@project) do %>
<% cache cache_key_for(@todolists, :todolists) do %>
<% cache cache_key_for(@todolists, :todolists_with_project) do %>
<% cache cache_key_for(@todolists, :todolists, current_user.visitor?) do %>
有些“爬虫”很不“规范”,对这种的我们用黑名单 + 简单行为识别,简单粗暴但有效:
# set in application.rb
# config.middleware.insert_after 'Rack::MethodOverride', 'BlockBadSpider'
class BlockBadSpider
def initialize(app)
@app = app
end
def call(env)
bad_spider_keywords = ["EasouSpider", "EMail Exractor"] # put more here is found other bad spider
# how to test: `curl -A 'EasouSpider' "http://localhost:3000"`
is_bad_spider = bad_spider_keywords.any? { |keyword| env["HTTP_USER_AGENT"].include?(keyword) }
if env["HTTP_USER_AGENT"].downcase.include?("spider") &&
env['CONTENT_TYPE'] == 'application/x-www-form-urlencoded' &&
env['REQUEST_METHOD'] == 'GET' &&
env['rack.input'] != nil &&
env["rack.request.form_input"] == env["rack.input"] &&
env["rack.request.form_vars"] != nil &&
(env["rack.request.form_hash"] == nil) || (env["rack.request.form_vars"] == 'NULL') # use 'NULL' make it easy to test with `curl`
acts_as_bad_spider = true
else
acts_as_bad_spider = false
end
if is_bad_spider or acts_as_bad_spider
# return 406 error when some request header is invalid
# https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error
# 406 Not Acceptable
[406, {}, ["The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request (406)"]]
else
@app.call(env)
end
end
end
楼主你用的什么 Entity,grape 的 Entity 非常灵活方便啊 内嵌复用什么的妥妥的
module Entities
class ProductService < Grape::Entity
expose :name
expose :description
expose :resource_category, using: ::Entities::ResourceCategory
end
end
楼主你需要的是 i18n,不是打 migration 的主意
赞!
用 obj.pretty_inspect
看到“We all love the feeling of getting things done.”这句不由的一阵共鸣如雷,加上@fredwu推荐,值得一读了。
被误导了,以为https://github.com/fredwu/flower 是聊天室的源码。。
跟 acts-as-taggable-on 无关,每个 gem 的大版本更新都不保证兼容,比如 rails(你从 rails2.x 直接换成 rails3.x 试试) 原则上使用所有 gem 都要指定版本(用"~> x.x.x"最稳妥),否则就是给自己埋坑
#23 楼 @ShiningRay #25 楼 @fredwu
我只说一点个人感觉 model desgin 这快“怪味道”最浓烈的地方:用 repository 和 entity 分离逻辑和持久化后,但在 controller 里又用到 repository 来 select 数据,难道还能说 repository 就跟业务逻辑“分离”了?
看到@ashchan 力推 Lotus 这个新的 web framework,大感兴趣研究一番。 但在看了 2 个 demo(https://gist.github.com/jodosha/9830002 和 https://github.com/sidonath/room-reservation)除了 在https://github.com/lotus/lotus 中介绍的 Microservices architecture(相当于 rails 的 engine 功能或者 Padrino 的 Mountable app)这个有意思外,其他的部分感觉是为了在做一个 framework 而创造 framework。
以下是吐槽(代码摘自和修改自 2 个 demo 以及 lotus 的 example)。
然后在每个 action class 里自己来实现一个 call(params) 方法里写处理逻辑(有种 rack 风格的味道),如:
class RoomsController
class Index
include RoomReservation::Action
expose :rooms
def initialize(repository: RoomRepository)
@repository = repository
end
def call(params)
@rooms = @repository.sorted_by_name
end
end
class Show
include RoomReservation::Action
def initialize(repository: RoomRepository)
@repository = repository
end
def call(params)
@model = @repository.find(params.fetch(:id))
end
end
虽然如设计理念上是容易测试了,如:
action = Show.new
response = action.call({ id: 23, key: 'value' })
但感觉是在把东西做复杂了,而且单独测试一个 contoller 的场景多吗?更多的恐怕是集成测试。
虽然有简单的 DSL 语法如:
class HomeController
include Lotus::Controller
action 'Index' do
expose :planet
def call(params)
@planet = 'World'
end
end
end
但看到一个 controller 文件里一大片的def call(params)
出现,DRY 的冲动就忍不住涌现。
一个折衷的方法是再定义个 dsl 语法来 DRY,例如:
class HomeController
include Lotus::Controller
def_action 'Index' do (params)
@planet = 'World'
end
end
例子:
module Rooms
class Index
include Lotus::View
layout :application
end
class Show
include Lotus::View
layout :application
end
class New
include Lotus::View
layout :application
def form
FormPresenter.new(locals[:form])
end
end
Create = New
class Edit
include Lotus::View
layout :application
def form
FormPresenter.new(locals[:form])
end
end
Update = Edit
end
然后每个 view 就需要对应的 template(如 rails 中的 view,也就是一些 erb)。 这个感觉有点像 cells(https://github.com/apotonick/cells)的概念。 但做 web 应用有必要这么复杂吗,除了某些场景需要复杂处理的 form 等(这时可以用 simple_form/formtastic/reform(room-reservation 的 demo 中用到)) 等 form helper,其余 90% 的就是个 erb。
对应 rails 的 view helpr 的是 Lotus::Presenter。 这个名字改得好,rails 中的"helper"这个词个人感觉有点太空泛了,让人不容易直接能联想到是跟 view 相关。但只是有必要每个 presneter 都也要是个类吗,我觉得在 rails 中比较好的解决方案是 Draper 或者 ActiveDecorator。
用 model 负责业务逻辑,持久化用 repository,详细见http://msdn.microsoft.com/en-us/library/ff649690.aspx
例子:
author = Author.new(name: 'Luca')
AuthorRepository.create(author)
author.name = "Luca"
AuthorRepository.update(author)
AuthorRepository.delete(author)
author = AuthorRepository.find(12)
要不要这么 raw,至少可以加个委托对象,如
author = Author.new(name: 'Luca')
author.repository.save
author.repository.delete
非常同意@luikore的看法:“正常脑子都会想到 instance 和 class 而不是 entity 和 repository, 语言提供的设施不用,自己造一堆是为何...”。
lotus 中 MVC 运作的概览,看一下从如何响应 new action 显示一个 form 到保存一个 room 到数据库所需要涉及的代码:
# controller
class RoomsController
class Index
include RoomReservation::Action
expose :rooms
def initialize(repository: RoomRepository)
@repository = repository
end
def call(params)
@rooms = @repository.sorted_by_name
end
end
class New
include RoomReservation::Action
expose :form
def call(params)
@form = RoomsFormFactory.create
end
end
class Create
include RoomReservation::Action
expose :form
def initialize(repository: RoomRepository)
@repository = repository
end
def call(params)
@form = RoomsFormFactory.create
room = @form.populate(params.fetch(:room), self)
@repository.persist(room)
redirect_to @router.path(:rooms)
end
end
# view
module Rooms
class Index
include Lotus::View
layout :application
end
class New
include Lotus::View
layout :application
def form
FormPresenter.new(locals[:form])
end
end
Create = New
end
# tempalte
<form action="<%= router.path(:rooms) %>" method="POST">
<div class="row">
<div class="small-12 columns <%= form.error_class(:name) %>">
<label>Name:
<input type="text" name="room[name]" value="<%= form.name %>">
</label>
<% form.errors_for(:name) do |message| %>
<small class="error"><%= message %></small>
<% end %>
</div>
</div>
<div class="row">
<div class="small-12 columns">
<a href="<%= router.path(:rooms) %>">Cancel</a>
<button type="submit">Create new room</button>
</div>
</div>
</form>
# presenter
class FormPresenter
include Lotus::Presenter
def error_class(key)
return nil if errors[key.to_sym].empty?
'error'
end
def errors_for(key, &block)
errors = self.errors[key.to_sym]
return if errors.empty?
yield errors.join(', ')
end
end
# form factory
module RoomsFormFactory
def self.create
RoomForm.new(Room.new)
end
end
# form
class RoomForm < Form
property :name
property :description
validates :name, presence: true
validates :description, presence: true
end
# model
class Room
include Lotus::Entity
end
# repository
class RoomRepository
include Lotus::Repository
def self.sorted_by_name
query { order(:name) }
end
end
对比 rails 以及 padrino 的实现,会感觉比较“啰嗦” 。 总体给我一种感觉是要把所用东西都要“类”化(作者是不是从 c#/java 转型到 ruby 的?),对于 web 开发却是有种化简为繁的感觉,如果不提供多些 DSL 或者 generator 来简化一些常见的类定义,将会是个 java 形态的 web framework。
楼主看看 07 年这篇 http://blog.aizatto.com/2007/05/21/activerecord-without-rails/ “Rails 中的 ActiveRecord”是可以脱离 Rails 的独立使用的 ORM,只是在 Rails 中使用会是 best practice,而 Rails 的 migration 只是 Rails 管理数据库变化的一个方式。
项目到一个节点后如某个版本后,用最新的 schema.rb 作为一个新的 migration,删掉旧的 migrations 就可以了。
ui 很不错,只是内容的 model 叫 Blog?应该叫 Post 吧,难道你不觉得在 BlogsController 里调@blogs = Blog.order
很怪吗
:thumbsup:
#14 楼 @chanshunli 要在 console 里测试 view 的 helper 的话,可以用helper.xx
:thumbsup:
关于数据查询缓存,可以推荐一下 flyerhzm 的 simple_cacheable,在某些需要缓存某个复杂计算结果的场景挺方便的,http://rails-bestpractices.com/blog/posts/24-simple_cacheable
@huacnlee 奇怪,内容怎么变了?我有加了段落标题的
很漂亮的成绩单,赞!
感谢分享,不过 has_sms_verification 追加的一些方法命名不是很好, 比如: user.messages 因为 user 可能已经有站内信息功能 (User has_many :messages),这样就会有冲突
user.deliver 这个命名相当不直观 (用户发送?发送什么?邮件?快递?)
最起码要这些应该都加入一个 sms 前缀或关键词,例如: user.deliver_sms user.sms_messages user.sms_verified? user.sms_verify! '123456' user.latest_sms_message
很好的整理