rails learning rake task, cron, whenever

Check how to use the rake task, cron, and whenever performed in the task and the points to remember in the code description at that time.

rake task

For defining the process you want to perform, you can execute the defined process without starting the application. cron It is possible to execute a command depending on the time, such as "execute a command of XX at XX". whenever A ruby ​​description method that makes cron easier to draw. The process is described in cron by `bundle exec whenever --update-crontab` the description written in schedule.rb.

How to write a rake task

The process to be set for the rake task this time is "If there is a publication waiting date that is in the past, the status will be changed to" Public "."

① First of all, because the article is waiting to be published, that is, the state of the article is wait_publish.

Article.wait_publish

(2) Next, you have to search for the one whose release date is in the past from (1). The fact that the publication date and time is in the past means that the publish_at time of the article is less than Time.current. Therefore, it becomes `` `'published_at <?', Time.current```.

A summary of this with a scope is written in the model

scope :viewable, -> { published.where('published_at < ?', Time.current) }

become. In other words, if you add viewable, you can search from the range where the release date is in the past. Therefore, articles that are waiting to be published and whose publication date and time are smaller than the current one

Article.wait_publish.viewable

Can be expressed like this

③ If the state is waiting for publication and the publication date and time is smaller than the present (in the past) Make it public (that is, make the state publish)

Article.wait_publish.viewable.publish

I think this will make it public, but there is another important point. Article.wait_publish.viewable does not mean that there is only one article. So you have to add publish to each one

When adding publish to each of the acquired multiple values use find_each find_each iteratively processes multiple acquired records You can also add publish! To each one by setting `(&: published!)`.

The code that combines (1), (2), and (3) and says, "If there is a publication waiting date that is in the past, the status will be changed to" public "."

Article.publish_wait.viewable.find_each(&:publish!)

Can be written as. If you write this in lib/tasks/article_state.rake, you can execute rake tasks.

lib/tasks/article_state.rake


namespace :article_state do
  desc 'If the article is waiting to be published and the publication date is in the past, change the status of the article to "Publish".'
  task change_publish: :environment do
    Article.publish_wait.viewable.find_each(&:publish!)
  end
end

Processing is now executed just by executing the article_state command in the terminal

How to write whenever

You have to run cron to launch the article_state command defined above once an hour, so use whenever (whenever is just like a cron translator)

db/schedule.rb


# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
require File.expand_path(File.dirname(__FILE__) + '/environment')
# Example:
rails_env = ENV['RAILS_ENV']|| :development
set :environment, rails_env
set :output, "#{Rails.root}/log/cron_log"
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
every :hour do
  rake 'article_state:change_publish'
end

# Learn more: http://github.com/javan/whenever
every :hour do
  rake 'article_state:change_publish'
end

This part is the code that changes_publish is executed in article_state once an hour. When the description is finished, use ``` bundle exec whenever --update-crontab` `` to reflect it in cron.

Points to remember

How to write enum / points

Be careful about the description method when adding new information to enum

article.rb


#Change before
enum state: { draft: 0, published: 1}

#NG
enum state: { draft: 0, published: 1, publish_wait: 2 }
#OK
enum state: %i{ draft published publish_wait }

The OK code is cleaner and easier to see. Try to write clean code

Easy to write when retrieving data with enum value

# NG
article.state = 'publish_wait'
Article.where(state: :publish_wait)

# OK
Article.publish_wait

If it is defined by enum, if you put the value of enum after Article like `Article.publish_wait``` without searching in` Article.where```, the data corresponding to that value will be displayed. Will be able to get

Other points ・ You can tell at a glance that it is defined by an enum. ・ The code is refreshing

Good way to write article_controller.rb ・ Points

Check how to write in the update action

article_controller.rb


def update
  authorize(@article)                                        #Point.1
   if @article.assign_attributes(article_params)             #Point.2
    @article.assign_publish_state unless @article.draft?     #Point.3
    @article.save!                                           
    flash[:notice] = 'Has been updated'
    redirect_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

Commentary point

Point.1 About authorize

A gem called Pundit allows you to use the authorization function `` `authorize. arthorize (@article)```If you define in the controller, that controller@It will check if you can use the article.

This time, `autherize (@article)` allows you to use `@ article```, so you can use @ articlewithout writing the definition of @ article```. Now available

Point.2 Reasons to use assign_attributes (article_params)

The assign_attributes (article_params) method is a method for changing attributes. At first glance it is almost the same as update (article_params), but there is one difference. that is

assign_attributes does not save data in the DB. update saves data in DB

Is. If you use update it will look like this

For update

article_controller.rb


def update
  authorize(@article)                                        
   if @article.update(article_params)                      #Access DB with update
    @article.assign_publish_state unless @article.draft?     
    @article.save!                                         #Access more DB with save
    flash[:notice] = 'Has been updated'
    redirect_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

For assign_attribute

article_controller.rb


def update
  authorize(@article)                                        
   if @article.assign_attributes(article_params)           #Do not access DB
    @article.assign_publish_state unless @article.draft?     
    @article.save!                                         #Access DB with save
    flash[:notice] = 'Has been updated'
    redirect_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

As mentioned above, the DB is accessed once with save anyway, and update accesses the DB more, which causes the processing to become heavy. So basically, the update method and save method do not coexist.

Point.3 About assign_publish_state (if statement in if statement)

This controller needs conditional branching processing to change the state to "waiting for publication" if the publication date is in the future than the current time, and to "public" if the publication date is the current time or past the current time.

Simply think

article_controller.rb


if @article.assign_attributes(article_params)           
  unless @article.draft? 
    @article.state = if @article.published_at <= Time.current
                       :published
                     else
                       :publish_wait
                     end
  end    
  @article.save!                                         
  flash[:notice] = 'Has been updated'
  redirect_to edit_admin_article_path(@article.uuid)
else
#abridgement
end

There are unless and if in if, which makes it difficult to understand the controller and increases the number of lines (Fat controller). So

@article.state = if @article.published_at <= Time.current
                       :published
                     else
                       :publish_wait
                     end

Is defined as one method for simplification

article_controller.rb


if @article.assign_attributes(article_params)           
  @article.assign_publish_state unless @article.draft?     
  @article.save!                                         
  flash[:notice] = 'Has been updated'
  redirect_to edit_admin_article_path(@article.uuid)
else
#abridgement
end

app/model/article.rb


def assign_publish_state
  self.state = if self.published_at <= Time.current
                     :published
                   else
                     :publish_wait
                   end
end

rspec points

I want to test whether the state changes from the original state depending on the date and time If the original state is a draft, waiting for publication, publishing, etc., conditional branching will eliminate the drill. Therefore, it is better to make the original state random and write a conditional branch that changes depending on the date and time from there so that you do not have to write code.

spec/factories/articles.rb


#Example
trait :random_state_article
  # NG
  state { Random.rand(1..2) }

  # OK
  state { Articles.states.values.sample }
 end

Random traits should be Articles.states.values.sample instead of Random.rand (1..2)

Recommended Posts

rails learning rake task, cron, whenever
[Rails] Implement rake task
[Rails] Status update using Rake task
Rails learning day 3
Rails learning day 4
Rails learning day 2
rails learning day 1
Rails Tutorial Chapter 3 Learning
Rails learning day 2 part 2
[Rails] Learning with Rails tutorial
Rails learning day 1 part 3
Rails learning day 3 part 2
Rails Tutorial Chapter 4 Learning
Rails Tutorial Chapter 1 Learning
Rails Tutorial Chapter 2 Learning
Rails learning Day 1 Part 2
About regular execution of rake task of rails application on heroku
[Rails] I tried to implement batch processing with Rake task
Ruby on rails learning record -2020.10.03
Ruby on rails learning record -2020.10.04
Ruby on rails learning record -2020.10.05
Ruby on rails learning record -2020.10.09
Ruby on Rails basic learning ①
Let's define a Rake task.
Ruby on rails learning record-2020.10.07 ②
Run Rails whenever with docker
Ruby on rails learning record-2020.10.07 ①
Ruby on rails learning record -2020.10.06
[Docker] Use whenever with Docker + Rails