Rails - Update attributes using rake task. Need he

2019-09-18 23:35发布

问题:

I am trying to use a rake task that will run every night (using Heroku Scheduler) and update some attributes if some certain criteria is met.

A little logic about the application:

The application allows users to "take the challenge" and read a book a week over the course of a year. It's pretty simple: users sign up, create the first book that they will read for their first week, then can enter what they're going to read next week. After they've "queued" up next week's book, that form is then hidden until it's been 7 days since their first book was created. At that point, the book that was queued up gets moved to the top of the list and marked as 'currently reading', while the previous 'currently reading' book moves down to the second position in the list.

And IF a user doesn't 'queue' a book, the system will automatically create one if it's been 7 days since the latest 'currently reading' book was created.

Where I need some advice

The place I'm currently stuck is getting the books to update attributes if it's been 7 days since the last 'currently reading' book was created. Below is my book model and the method update_queue is what gets called during the rake task. Running the rake task currently gives no errors and properly loops through the code, but it just doesn't change any attribute values. So I'm sure the code in the update_queue method is not correct somewhere along the lines and I would love your help troubleshooting the reason why. And how I'm testing this is by adding a book then manually changing my system's date to 8 days ahead. Pretty barbaric, but I don't have a test suite written for this application & it's the easiest way to do it for me :)

class Book < ActiveRecord::Base
  attr_accessible :author, :date, :order, :title, :user_id, :status, :queued, :reading
  belongs_to :user

 scope :reading_books, lambda {
    {:conditions => {:reading => 1}}
  }

  scope :latest_first, lambda {
    {:order => "created_at DESC"}
  }


  def move_from_queue_to_reading
    self.update_attributes(:queued => false, :reading => 1);
  end

  def move_from_reading_to_list
    self.update_attributes(:reading => 0);
  end

  def update_queue
    days_gone = (Date.today - Date.parse(Book.where(:reading => 1).last.created_at.to_s)).to_i

    # If been 7 days since last 'currently reading' book created
    if days_gone >= 7

        # If there's a queued book, move it to 'currently reading'
        if Book.my_books(user_id).where(:queued => true)
            new_book = Book.my_books(user_id).latest_first.where(:queued => true).last
            new_book.move_from_queue_to_reading
            Book.my_books(user_id).reading_books.move_from_reading_to_list

        # Otherwise, create a new one
        else
            Book.my_books(user_id).create(:title => "Sample book", :reading => 1)

        end
    end
  end

My rake task looks like this (scheduler.rake placed in lib/tasks):

task :queue => :environment do
  puts "Updating feed..."
  @books = Book.all
  @books.each do |book|
    book.update_queue
  end
  puts "done."
end

回答1:

I would move the update_queue logic to the User model and modify the Book model somewhat, and do something like this:

# in book.rb
# change :reading Boolean field to :reading_at Timestamp
scope :queued, where(:queued => true)
scope :earliest_first, order("books.created_at")
scope :reading_books, where("books.reading_at IS NOT NULL")

def move_from_queue_to_reading
  self.update_attributes(:queued => false, :reading_at => Time.current);
end

def move_from_reading_to_list
  self.update_attributes(:reading_at => nil);
end


# in user.rb
def update_queue
  reading_book = books.reading_books.first
  # there is an edge-case where reading_book can't be found
  # for the moment we will simply exit and not address it
  return unless reading_book 

  days_gone = Date.today - reading_book.reading_at.to_date

  # If less than 7 days since last 'currently reading' book created then exit
  return if days_gone < 7

  # wrap modifications in a transaction so they can be rolled back together
  # if an error occurs
  transaction do
    # First deal with the 'currently reading' book if there is one
    reading_book.move_from_reading_to_list

    # If there's a queued book, move it to 'currently reading'
    if books.queued.exists?
      books.queued.earliest_first.first.move_from_queue_to_reading
    # Otherwise, create a new one
    else
      books.create(:title => "Sample book", :reading_at => Time.current)
    end
  end
end

Now you can have the Heroku scheduler run something like this once a day:

User.all.each(&:update_queue)

Modify User.all to only return active users if you need to.

Oh, and you can use the timecop gem to manipulate times and dates when testing.



回答2:

Probably the reason is that, the program flow is not going inside the if days_gone >= 7 condition.

you could check this in two ways

1 - Simple and easy way (but not a very good way)

use p statements every were with some meaning full text

Ex:

def update_queue
    days_gone = (Date.today - Date.parse(Book.where(:reading => 1).last.created_at.to_s)).to_i

    p "days gone : #{days_gone}"   

    # If been 7 days since last 'currently reading' book created
    if days_gone >= 7
        p "inside days_gone >= 7"
    etc... 

2 - Use ruby debugger and use debug points

in Gem file add

gem 'debugger'

and insert break points where ever needed

def update_queue
        days_gone = (Date.today - Date.parse(Book.where(:reading => 1).last.created_at.to_s)).to_i
       debugger

more help

HTH