fangtingting
12/17/2017 - 8:10 AM

Rails Controller 代码示例

rails controller 代码示例

# rails g controller welcome
class UsersController < ApplicationController

  # 过滤器
  before_action :require_login
  skip_before_action :require_login, only: [:new, :create]
  around_action :wrap_in_transaction, only: :show
  before_action do |controller|
    unless controller.send(:logged_in?)
      flash[:error] = "You must be logged in to access this section"
      redirect_to new_login_url
    end
  end

  # 设置本地化
  before_action :set_locale
  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end

  # 异常处理
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
  def record_not_found
    render plain: "404 Not Found", status: 404
  end

  # 设置布局
  # 查找布局时,Rails 首先查看 app/views/layouts 文件夹中是否有和控制器同名的文件。
  # 例如,渲染 PhotosController 控制器中的动作会使用 app/views/layouts/photos.html.erb(或 app/views/layouts/photos.builder)。
  # 如果没找到针对控制器的布局,Rails 会使用 app/views/layouts/application.html.erb 或 app/views/layouts/application.builder。
  # 如果没有 .erb 布局,Rails 会使用 .builder 布局(如果文件存在)。
  layout :products_layout
  layout "product", except: [:index, :rss]
  layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }

  def products_layout
    @current_user.special? ? "special" : "products"
  end

  # 访问一个controller时params[:controller]、params[:action]、controller_name这些变量是可以直接调用的

  def index
    # 新建
    obj=User.new(name: value)
    obj.save

    obj=User.create(name: value)

    # 更新
    obj.update(attribute1: value1, attribute2: value2,..)

    # 删除
    obj.destroy

    # 查询,find_by查询到空返回nil
    # Model.find(primary_key|array_of_primary_key),未找到匹配的记录会抛出 ActiveRecord::RecordNotFound 异常。find是select *所有字段
    # Model.take会获取一个记录,不考虑任何顺序,没找到记录,Model.take 不会抛出异常,而是返回 nil,Model.take! 会抛出 ActiveRecord::RecordNotFound 异常
    # Model.first(last) 获取按主键排序得到的第一个记录, 如果没找到匹配的记录,不会抛出异常,而是返回 nil,Model.first!(last!) 会抛出 ActiveRecord::RecordNotFound 异常
    # Model.find_by 获取满足条件的第一个记录
    User.find_by(name: "test",attribute2: value2,..)
    User.find_by_name("test")
    User.find_by_name_and_attribute2("test",value2)

    # 使用find_all_by 获取所有满足条件的记录,查询到空返回[]
    User.find_all_by(name: "test",attribute2: value2,..)
    User.find_all_by_name("test")
    User.find_all_by_name_and_attribute2("test",value2)

    # Model.find(array_of_primary_key) 方法可接受一个由主键组成的数组,返回一个由主键对应记录组成的数组,mysql数组过长会截断数据,oracle报错。
    # Model.take(limit) 方法获取 limit 个记录,不考虑任何顺序。
    # Model.first(limit) 方法获取按主键排序的前 limit 个记录。Model.last(limit) 
    # Model.all获取所有对象
    # find_each 获取 1000 个记录,然后把每个记录传入代码块。batch_size:可以规定记录量,start:从哪里开始
    User.find_each(start: 2000,batch_size: 5000) {|e| ...}
    # find_in_batches 把整批记录作为一个数组传入代码块,而不是单独传入各记录。在下面的例子中,会把 1000 个单据一次性传入代码块,让代码块后面的程序处理剩下的单据。
    User.find_in_batches{|users| ...}

    # where查询,where查询到空返回的是[]空数组
    # Model.where(“attribute1=? and attribute2=?”, value1,value2)
    # Model.where(attribute1: value1,attribute2: value2)
    # Model.where(attribute: array) 使用IN子句查询记录
    # Model.where(attribute: (1..10)) 使用between。
    User.where("orders_count = ?", params[:orders])
    User.where("orders_count = ? and id = ?", params[:orders],1)
    User.where("created_at >= :start_date AND created_at <= :end_date",{start_date: params[:start_date], end_date: params[:end_date]})
    User.where('locked' => true) || User.where(locked: true)
    User.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
    User.where(orders_count: [1,3,5])

    # not条件用where.not构建
    User.where.not(author: "author")

    # 排序
    Client.order(orders_count: :asc, created_at: :desc) || Client.order("orders_count ASC, created_at DESC")

    # 要查询部分字段,可使用 select 方法
    # Model.select(sql)
    # Model.select(:attribute1,...)
    Client.select("viewable_by, locked").distinct

    # 限量和偏移
    Client.limit(5).offset(30)

    # group by
    Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)").having("sum(price) > ?", 100)

    # 删除某个条件
    Post.where('id > 10').limit(20).order('id asc').unscope(:order)
    Post.where(id: 10, trashed: false).unscope(where: :id)

    # reorder 方法覆盖原来的 order 条件
    Post.find(10).comments.reorder('name')

    # rewhere 方法覆盖前面的 where 条件
    Post.where(trashed: true).rewhere(trashed: false)

    # reverse_order 方法翻转 ORDER 子句的条件(ORDER BY name DESC)
    Client.where("orders_count > 10").order(:name).reverse_order

    # none 返回一个可链接的关系,没有相应的记录。none 方法返回对象的后续条件查询,得到的还是空关系。如果想以可链接的方式响应可能无返回结果的方法或者作用域,可使用 none 方法。
    Post.none # returns an empty Relation and fires no queries.

    #连接数据表,有joins的方式也有includes方式,非sql的joins是内连接,includes方式是左外连接,推荐使用includes
    # Model.joins(sql) #可以指定是左连接还是右连接
    # Model.joins(:relation_table) select model的全部字段,这种是内连接。
    # Model.joins(:relation_table1,:relation_table2,...)
    # Model.joins(relation_table: :relation_relation_table)连接嵌套关联
    User.joins(:posts).where(posts: { author: "author" })
    Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')
    Post.joins(:category, :comments)
    Post.joins(comments: :guest)
    Category.joins(posts: [{ comments: :guest }, :tags])
    time_range = (Time.now.midnight - 1.day)..Time.now.midnight
    Client.joins(:orders).where('orders.created_at' => time_range)
    # 按需加载关联
    # Model.includes(:relation1,:relation2,...)
    # Model.includes(relation:[{relation_relation1: :relation_relation1_relation},:relation_relation2]).find(options) #使用hash指定嵌套关联
    Client.includes(:address).limit(10)
    Post.includes(:category, :comments)
    Category.includes(posts: [{ comments: :guest }, :tags]).find(1)
    # 虽然 Active Record 允许使用 joins 方法指定用于按需加载关联上的条件,但是推荐的做法是使用下面的方法
    Post.includes(:comments).where("comments.visible" => true)
    #使用include按需加载,减少查询次数
    #查询11次
    clients = Client.limit(10)
    clients.each do |client|
      puts client.address.postcode
    end
    #查询2次
    clients = Client.includes(:address).limit(10)
    clients.each do |client|
      puts client.address.postcode
    end


    # and条件使用方法 
    User.active.merge(User.inactive)

    # 查找或构建新对象: find_or_create_by! 方法,如果新纪录不合法,会抛出异常。find_or_initialize_by类似,调用的是new
    Client.find_or_create_by(first_name: 'Andy')

    # 通过sql查询
    Client.find_by_sql("SELECT * FROM clients
      INNER JOIN orders ON clients.id = orders.client_id
      ORDER BY clients.created_at desc")
    Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")

    # pluck 方法可以在模型对应的数据表中查询一个或多个字段,其参数是一组字段名,返回结果是由各字段的值组成的数组。
    # pluck跟select 方法不一样,pluck 直接把查询结果转换成 Ruby 数组,不生成 Active Record 对象,可以提升大型查询或常用查询的执行效率。但 pluck 方法不会使用重新定义的属性方法处理查询结果。
    Client.pluck(:id, :name) || Client.select(:id, :name).map { |c| [c.id, c.name] }
    Client.where(active: true).pluck(:id) || Client.select(:id).map(&:id)

    # 检查对象是否存在,在模型或关系中检查存在性时还可使用 any? 和 many? 方法
    Client.exists?(id: [1,2,3]) || Client.exists?(name: ['John', 'Sergei'])
    Client.where(first_name: 'Ryan').exists?

    # 计算
    Client.where(first_name: 'Ryan').count
    Client.average("orders_count")
    Client.minimum("age")
    Client.maximum("age")
    Client.sum("orders_count")

    # 直接获取数据表的主键
    Client.ids

    # 设置会话和cookies
    session[:current_user_id] = user.id
    cookies[:commenter_name] = @comment.author
    flash[:notice] = "You have successfully logged out."
    flash.now[:error] = "Could not save client"
    # Flash 消息还可以直接在转向中设置。可以指定 :notice、:alert 或者常规的 :flash
    redirect_to root_url, notice: "You have successfully logged out."
    redirect_to root_url, alert: "You're stuck here!"
    redirect_to root_url, flash: { referral_code: 1234 }

    # 渲染数据
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render xml: @users}
      format.json { render json: @users}
    end

  end

  def create
    # params Hash 总有 :controller 和 :action 两个键,但获取这两个值应该使用 controller_name 和 action_name 方法
    params[:controller].controller_name
    params[:action].action_name

    # 健壮参数使用
    person = People.find(params[:id])
    person.update!(person_params)
  end

  def strong_params
    params.require(:person).permit(:name, :age)
    params.permit(:name, { emails: [] },friends: [ :name,{ family: [ :name ], hobbies: [] }])
    # 使用 accepts_nested_attributes_for 方法可以更新或销毁响应的记录。这个方法基于 id 和 _destroy 参数
    params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])
    # 假设有个参数包含产品的名字和一个由任意数据组成的产品附加信息 Hash,希望过滤产品名和整个附加数据 Hash。健壮参数不能过滤由任意键值组成的嵌套 Hash,不过可以使用嵌套 Hash 的键定义过滤规则
    params.require(:product).permit(:name, data: params[:product][:data].try(:keys))
    # 强制params,允许所有的参数
    params.permit!
  end

  # 数据流及文件下载,想把数据以数据流的形式发送给客户端,可以使用 send_data 方法,想发送硬盘上已经存在的文件,可以使用 send_file 方法
  # 不建议通过 Rails 以数据流的方式发送静态文件,你可以把静态文件放在服务器的公共文件夹中,使用 Apache 或其他服务器下载效率更高,因为不用经由整个 Rails 处理。
  def download_pdf
    client = Client.find(params[:id])
    send_data generate_pdf(client),
    filename: "#{client.name}.pdf",
    type: "application/pdf"


    send_file("#{Rails.root}/files/clients/#{client.id}.pdf",
      filename: "#{client.name}.pdf",
      type: "application/pdf")

    # 使用 REST 的方式下载文件
    respond_to do |format|
      format.html
      format.pdf { render pdf: generate_pdf(@client) }
    end
    # 为了让这段代码能顺利运行,要把 PDF MIME 加入 Rails。在 config/initializers/mime_types.rb 文件中加入下面这行代码即可
    # Mime::Type.register "application/pdf", :pdf
  end
  def generate_pdf(client)
    Prawn::Document.new do
      text client.name, align: :center
      text "Address: #{client.address}"
      text "Email: #{client.email}"
    end.render
  end 
  # 渲染
  def render
    render nothing: true
    # 渲染动作的视图,edit.html.erb 模板
    render "edit"
    render :edit
    render action: :edit
    render "edit.html.erb"
    render action: "edit"
    render action: "edit.html.erb"
    render "books/edit"
    render "books/edit.html.erb"
    render template: "books/edit"
    render template: "books/edit.html.erb"
    render "/path/to/rails/app/views/books/edit"
    render "/path/to/rails/app/views/books/edit.html.erb"
    render file: "/path/to/rails/app/views/books/edit"
    render file: "/path/to/rails/app/views/books/edit.html.erb"

    # 渲染其他控制器中的动作模板
    render "products/show"
    render template: "products/show"
    # 渲染任意文件,默认情况下,渲染文件时不会使用当前程序的布局。如果想让 Rails 把文件套入布局,要指定 layout: true 选项。
    render "/u/apps/warehouse_app/current/app/views/products/show"
    render file: "/u/apps/warehouse_app/current/app/views/products/show"

    #  :inline 选项指定了 ERB 代码,render 方法就不会渲染视图
    #  但是很少这么做。在控制器中混用 ERB 代码违反了 MVC 架构原则,也让程序的其他开发者难以理解程序的逻辑思路。请使用单独的 ERB 视图
    render inline: "<% products.each do |p| %><p><%= p.name %></p><% end %>"
    render inline: "xml.p {'Horrid coding practice!'}", type: :builder

    # 渲染文本,渲染纯文本主要用于 Ajax 或无需使用 HTML 的网络服务。
    # 默认情况下,使用 :plain 选项渲染纯文本,不会套用程序的布局。如果想使用布局,可以指定 layout: true 选项。
    render plain: "OK"

    # 渲染html
    render html: "<strong>Not Found</strong>".html_safe
    render json: @product
    render xml: @product
    render js: "alert('Hello Rails');"

    render text: "name is yong"
    render text: "<script>alert('success');location.reload();</script>"

    # 调用 render 方法时使用 :body 选项,可以不设置内容类型,把原始的内容发送给浏览器
    render body: "raw"

    #1、content_type: 默认情况下,Rails 渲染得到的结果内容类型为 text/html;如果使用 :json 选项,内容类型为 application/json;如果使用 :xml 选项,内容类型为 application/xml。如果需要修改内容类型,可使用 :content_type 选项
    render file: filename, content_type: "application/rss"
    #2、layout:使用指定布局
    render "edit", layout: "special_layout"
    render layout: false
    #3、location:可以设置 HTTP Location 报头,跳转地址
    render xml: photo, location: photo_url(photo)
    #4、status:Rails 会自动为生成的响应附加正确的 HTTP 状态码(大多数情况下是 200 OK)
    render "edit", status: 200

    # 避免双重渲染错误,千万别用 && return 代替 and return,因为 Ruby 语言操作符优先级的关系,&& return 根本不起作用。
    render action: "special_show" and return

    redirect_to photos_url
    redirect_to photos_path, status: 301
    redirect_to(@product)
  end
end

#  render 和 redirect_to 的区别
# 有些经验不足的开发者会认为 redirect_to 方法是一种 goto 命令,把代码从一处转到别处。这么理解是不对的。执行到 redirect_to 方法时,代码会停止运行,等待浏览器发起新请求。你需要告诉浏览器下一个请求是什么,并返回 302 状态码。