rails4 concerns

最近把rails4拉下来跑了一下,发现在controllersmodels下多了一个新的文件夹concerns

原来concerns里面放的就是之前写rails库经常用到的ActiveSupport::Concern,解决的问题是models或者controllers太臃肿了(当业务量庞大的时候),不过看起来只是代码的抽离。

相关链接:

f6bbc3f582

put-chubby-models-on-a-diet-with-concerns

顺带提一下,路由里面也加了一个concern

https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/routing/mapper.rb#L1629

concern :commentable do
  resources :comments
end

concern :image_attachable do
  resources :images, only: :index
end

# These concerns are used in Resources routing:

resources :messages, concerns: [:commentable, :image_attachable]

# or in a scope or namespace:

namespace :posts do
  concerns :commentable
end

Rails4 Activesupport Changelog

activesupport

  • Date.beginning_of_week thread local and beginning_of_week application config option added (default is Monday).
    Pull Request #5339

    thread local并且多增加了一个配置选项,可以设置一周的起点是星期几,默认是星期一

    class Application < Rails::Application
      config.beginning_of_week = :wednesday
    end
  • An optional block can be passed to config_accessor to set its default value.
    Pull Request #7645

    config_accessor可以设置默认值:

    class User
      include ActiveSupport::Configurable
      config_accessor :hair_colors do
        [:brown, :black, :blonde, :red]
      end
    end

    User.hair_colors # => [:brown, :black, :blonde, :red]
  • An optional block can be passed to Hash#deep_merge. The block will be invoked for each duplicated key and used to resolve the conflict.
    Pull Request #7628

    Hash#deep_merge支持block:

    h1 = {x: {y: [4,5,6]}, z: [7,8,9]}
    h2 = {x: {y: [7,8,9]}, z: "xyz"}

    h1.deep_merge(h2) do |key, old, new|
      Array.wrap(old) + Array.wrap(new)
    end
    #=> {:x => {:y => [4, 5, 6, 7, 8, 9]}, :z => [7, 8, 9, "xyz"]}
  • ActiveSupport::Deprecation is now a class. It is possible to create an instance of deprecator. Backwards compatibility has been preserved.
    Pull Request #6348

    You can choose which instance of the deprecator will be used.

    deprecate :method_name, :deprecator => deprecator_instance

    You can use ActiveSupport::Deprecation in your gem.

    require 'active_support/deprecation'
    require 'active_support/core_ext/module/deprecation'

    class MyGem
      def self.deprecator
        ActiveSupport::Deprecation.new('2.0', 'MyGem')
      end

      def old_method
      end

      def new_method
      end

      deprecate :old_method => :new_method, :deprecator => deprecator
    end

    MyGem.new.old_method
    # => DEPRECATION WARNING: old_method is deprecated and will be removed from MyGem 2.0 (use new_method instead). (called from <main> at file.rb:18)
  • ERB::Util.html_escape encodes single quote as #39. Decimal form has better support in old browsers.
    Pull Request #7513
  • ActiveSupport::Callbacks: deprecate monkey patch of object callbacks. Using the #filter method.
    Pull Request #7560

https://github.com/rails/rails/blob/master/activesupport/CHANGELOG.md

rails中的一些技巧

这篇文章可以说是10-things-you-didnt-know-rails-could-do10-most-underused-activerecord-relation-methods/的读书笔记,只列出我不熟悉的,所以如果想看全部内容可以参考上面的链接

1)pluck

原先我代码里确实充斥着published_book_titles = Book.published.map(&:title)

Good:

published_book_titles = Book.published.pluck(:title)

2)find_by (rails 4 only)

很多时候,我找单条记录一般是这样:

Book.where(:title => 'Three Day Road', :author => 'Joseph Boyden').first

更常用的是使用dynamic finder:

Book.find_by_title_and_author 'Three Day Road', 'Joseph Boyden'

如果使用find_by就可以这样:

Book.find_by(:title => 'Three Day Road', :author => 'Joseph Boyden')
# 或者你可以写成这样
Book.where(:title => 'Three Day Road').find_by :author => 'Joseph Boyden'

参考这边的讨论,可以看出find_by的目的是取代使用method_missing的dynamic finder,find_by的实质就是where().first,所以如果你不想等rails4,自己写一个也行。

3)none (rails 4 only)

其实不是返回空数组,而是返回一个NullRelation的实例,他的应用场景在于期望返回0条记录但是又可以使用chainable(查询链),官方的example:

@posts = current_user.visible_posts.where(:name => params[:name])
# => the visible_post method response has to be a chainable Relation

def visible_posts
  case role
  if 'Country Manager'
    Post.where(:country => country)
  if 'Reviewer'
    Post.published
  if 'Bad User'
    Post.none # => returning [] instead breaks the previous code
  end
end

并且类似于Post.none.published,不会向数据库发起请求。相关讨论pull request

4)scoping 和 unscoped

scoping这个用的不多

Comment.where(:post_id => 1).scoping do
  Comment.first # SELECT * FROM comments WHERE post_id = 1
end

unscoped用来取消default_scope的作用

class Post < ActiveRecord::Base
  def self.default_scope
    where :published => true
  end
end

Post.all          # Fires "SELECT * FROM posts WHERE published = true"
Post.unscoped.all # Fires "SELECT * FROM posts"

Post.unscoped {
  Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
}

需要注意的是scope chain不受影响,下面两个等价:

Post.unscoped.published
Post.published

5)first_or_create 和 first_or_initialize

# Find the first user named Penélope or create a new one.
User.where(:first_name => 'Penélope').first_or_create

User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')

6)merge

有篇文章提到了merge这个方法,我特意去看了源码,还发现了几个有意思的地方。

关于default_scope
如果定义多个default_scope,他们会合并在一起,这里就用了merge:

class Article < ActiveRecord::Base
  default_scope { where(:published => true) }
  default_scope { where(:rating => 'G') }
end

还可以自定义default_scope方法:

class Article < ActiveRecord::Base
  def self.default_scope
    # Should return a scope, you can call 'super' here etc.
  end
end

merge在ActiveRecord::SpawnMethods模块中:

# Merges in the conditions from other,
# if other is an ActiveRecord::Relation.
Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) )

# 如果传入的是数组则返回数组
recent_posts = Post.order('created_at DESC').first(5)
Post.where(:published => true).merge(recent_posts)

merge还是很有用的,类似的方法还有except和only

except:

# discards the order condition
Post.order('id asc').except(:order)

# discards the where condition but keeps the order
Post.where('id > 10').order('id asc').except(:where)

only:

# discards the order condition
Post.order('id asc').only(:where)

# uses the specified order
Post.order('id asc').only(:where, :order)

7)rake notes

原先我都是用一些TODO list软件,这里可以直接通过rake命令来提醒自己

class UserController < ApplicationController
  # TODO: BalaBala...
  # FIXME: ...
  # OPTIMIZE: ...
  # CUSTOM: ...
end

# 使用 use it!
rake notes
rake notes:todo
rake notes:fixme
rake notes:custom ANNOTATION=CUSTOM

8)rails r

有时候我们想进rails终端做简单的测试,很多时候都是rails c
如果不复杂可以直接用:

rails r 'p [Article, Comment, User].map(&:count)'
# => [0,0,0]

9)直接使用helper测试helper方法

原先我都是通过include xxx模块在console中调用helper方法的,原来还有这么简单的方式:

helper.time_ago_in_words 3.days.ago
# '3 days'

10)rails generate resource command

rails g resource user name:index email:uniq token:string{6} bio:text

rails g resource article user:references subject body:text

rails g resource comment user:belongs_to article:belongs_to body:text

11)inquiry,except

inquiry:

env = "production".inquiry
env.production?  # => true
env.development? # => false

except:

params = {controller: 'home', action: 'index', from: 'Google'}
params.except :controller, :action
# => {:from => 'Google'}