从控制器移动API调用到rails中的模型

时间:2016-10-26 02:48:50

标签: ruby-on-rails api

我正在制作一个Book Club应用程序。用户可以上传他们想要与其他人一起阅读的书籍。我从Google Books Api引入了图书信息,目前在控制器中有API调用。我知道这很丑陋而且非常难以驾驶,但是很难让它从模型中发挥作用。你会建议什么作为制作这种清洁剂的最佳重构?

new.html.erb -from books

  <%= form_for :book, url: books_path do |f| %>
    <fieldset>
      <h1 class="text-center">Add A Book</h1>

      <div class="form-group">
        <label class="col-md-4 control-label" for="name">Title</label>
        <div class="col-md-8">
          <%= f.text_field :title, required: true, class: "form-control" %><br>
        </div>
      </div>

      <div class="form-group">
        <label class="col-md-4 control-label" for="genre">Genre</label>
        <div class="col-md-8">
          <%= f.select :genre, [["Sci-fi", "Sci-fi"], ["Fantasy", "Fantasy"], ["Comic", "Comic"], ["Manga", "Manga"]], class: "form-control" %>
        </div>
      </div>

      <div class="form-group">
        <div class="col-md-12">
          <%= f.submit "Create Book", class: "btn btn-success" %>
        </div>
      </div>

    </fieldset>
  <% end %>

books_controller.rb

def create
  @user = current_user
  find_book
  redirect_to root_path
end

require "openssl"
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE

def find_book
  tempBook = params[:book][:title]
  tempGenre = params[:book][:genre]
  url = "https://www.googleapis.com/books/v1/volumes?q=" + tempBook + "&key=secret_key"
  uri = URI(url)
  response = Net::HTTP.get(uri)
  book_data = JSON.parse(response)    
  b = Book.new
  b.user_id = @user.id
  b.title = book_data["items"][0]["volumeInfo"]["title"]
  b.genre = tempGenre
  b.author = book_data["items"][0]["volumeInfo"]["authors"][0]
  b.publisher =  book_data["items"][0]["volumeInfo"]["publisher"]
  b.publication_date =  book_data["items"][0]["volumeInfo"]["publishedDate"]
  b.synopsis =  book_data["items"][0]["volumeInfo"]["description"]
  b.image = book_data["items"][0]["volumeInfo"]["imageLinks"]["thumbnail"]
  @book = b.save
end

book.rb

class Book < ActiveRecord::Base
  belongs_to :user
  has_many :reviews

  def set_user(user)
    self.user_id = user.id 
    self.save    
  end

end

它以这种方式工作,但是很难看,我应该隐藏我的钥匙,而不是让它打开。

我尝试将函数放在模型中,并在show方法中将标题和类型声明为变量,但它们没有传递给模型,所以它没有工作。

谢谢!

这是我尝试的代码,但它没有用。 @tempBook是零,所以它弄乱了模型。我假设在获取变量之前模型正在运行?

book.rb

class Book < ActiveRecord::Base
  belongs_to :user
  has_many :reviews

  def set_user(user)
    self.user_id = user.id 
    self.save    
  end

  require "net/http"
  require "json"

  require "openssl"
  OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE

  def self.find_book
    url = "https://www.googleapis.com/books/v1/volumes?q=" + @tempBook + "&key=SECRET_KEY"
    uri = URI(url)
    response = Net::HTTP.get(uri)
    book_data = JSON.parse(response)    
    b = Book.new
    b.user_id = @user.id
    b.title = book_data["items"][0]["volumeInfo"]["title"]
    b.genre = @tempGenre
    b.author = book_data["items"][0]["volumeInfo"]["authors"][0]
    b.publisher =  book_data["items"][0]["volumeInfo"]["publisher"]
    b.publication_date =  book_data["items"][0]["volumeInfo"]["publishedDate"]
    b.synopsis =  book_data["items"][0]["volumeInfo"]["description"]
    b.image = book_data["items"][0]["volumeInfo"]["imageLinks"]["thumbnail"]
    @book = b.save
  end

end

books_controller.rb

  def create
    @tempBook = params[:book][:title]
    @tempGenre = params[:book][:genre]
    @user = current_user
    Book.find_book
    redirect_to root_path
  end

2 个答案:

答案 0 :(得分:4)

您应该将google代码解压缩到lib目录中的某个类,将该类实例的实例化放入图书模型,然后在那里调用find_book方法。您可以将api的配置密钥放入yml文件中。

#lib/google_book.rb
class GoogleBook
  def initialize(info)
  end

  def find_book
    url = "https://www.googleapis.com/books/v1/volumes?q=" + tempBook + "&key=secret_key"
    uri = URI(url)
    response = Net::HTTP.get(uri)
    book_data = JSON.parse(response) 
    #rest of code
  end
end

#book.rb
class Book < ActiveRecord::Base
  belongs_to :user
  has_many :reviews

  def set_user(user)
    self.user_id = user.id 
    self.save    
  end

  def retrieve_google_book
    google_book = GoogleBook.new(some_params)
    google_book.find_book
  end

end

答案 1 :(得分:2)

步骤1简化代码并使其更具可读性:

book_data = book_data["items"][0]["volumeInfo"]
# then you can write the shorter version:
b.title = book_data["title"] # etc

第2步:秘密密钥通常最适合作为环境变量 Google了解如何在您的平台上设置环境变量。

使用它的方法是:

# Note that I've also added 'string interpolation' here
# (google that phrase to find out more)
url = "https://www.googleapis.com/books/v1/volumes?q=#{tempBook}&key=#{ENV['BOOK_FIND_SECRET_KEY']}"

注意:这些都是要改变的小事。我想提供更多帮助,但在模型中尝试时需要更多详细信息(请参阅问题评论)。

第3步@variables不应该被使用(除非他们应该使用)。

@variables很特别......我们在控制器中使用它们将值传递给视图 - 这是因为从控制器到视图的数据是神奇的切换。但是在我们所有其他的ruby代码中 - 将数据作为实际参数传递给方法会更好。例如,如果您的图书模型有一个名为find_book的方法 - 而不是使用@tempBook,那么您就会有一个名为book_name或类似的参数。我也将这一大方法分解为较小的方法 - 每种方法都做一件具体的事情。这是一个只修改现有代码的示例:

# Note: generally it's accepted practice to put these includes
# at the top of the file outside of the class definition
require "net/http"
require "json"

require "openssl"

class Book < ActiveRecord::Base
  # Store the secret key in this constant so you don't have to 
  # keep typing the ENV-var name each time you use it
  SECRET_KEY = ENV['BOOK_FETCH_SECRET_KEY']

  belongs_to :user
  has_many :reviews

  # I don't know what this is, so I've left it...
  # but it probably shoudln't go here...
  OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE

  # Make the book-url into a method - abstract out this complexity 
  # so the code for the main find_book method is much easier to read.
  # Notice how we pass in the book_name as an argument
  def self.book_url(book_name)
    "https://www.googleapis.com/books/v1/volumes?q=#{book_name}&key=#{SECRET_KEY}"
  end

  # Fetching the book via the URL is kind of a stand-alone thing to do
  # so it's easy to turn it into a method - that just accepts the 
  # book title, and returns the parsed book data
  def self.fetch_book_data(book_name)
    uri = URI(book_url(book_name))
    response = Net::HTTP.get(uri)
    JSON.parse(response)    
  end

  # and here's what's left for find_book
  # note how we name and use the arguments
  def self.find_book(user, book_name, genre)
    book_data = fetch_book_data(book_name)
    the_book = book_data["items"][0]["volumeInfo"]
    b = Book.new
    b.user_id = user.id
    b.title = the_book["title"]
    b.genre = genre
    b.author = the_book["authors"][0]
    b.publisher =  the_book["publisher"]
    b.publication_date =  the_book["publishedDate"]
    b.synopsis =  the_book["description"]
    b.image = the_book["imageLinks"]["thumbnail"]
    b.save
    # return the new book at the end of the method
    b
  end    
end

现在在控制器中,我们可以非常简单地调用此代码:

def find_book
  # We just call the method on Book - passing in our params as the arguments
  @book = Book.find_book(current_user, params[:book][:title], params[:book][:genre])
end

注意:我真的很喜欢另一个答案的想法,即让GoogleBook类进行提取 - 您可以这样做并将他的提取代码放入fetch_book_data(或等效的)以更好地分离关注点