Creating a task in ruby on rails - ruby-on-rails

I want to create a CRON task for daily report. I need guidance where to create my class in my project (in which folder). How to instantiate an object from rails console for the same class. Will that class inherit application controller? I would also like to know since i will be querying my database so would my models be directly accessible in this file or somehow i have to include them like we do in django?
I have created a class /lib/tasks/daily_report.rb. But i am unable to understand how will i use that file to create a task.
module Reports
class Report
class << self
def collect_data
row_data = []
headers = ["Mobile", "Buildings", "Owners", "Tenants", "Members", "Total People"]
row_data.push(*headers)
puts "in side collect data"
date = Date.today.to_s
mobile = ["mobiles"]
for i in mobile do
row = []
row << i
build_count = Buildings.where(created_at: date, added_by: i).count
row << build_count
puts "build_count"
owners_count = Residents.where(created_at: date, added_by: i, role: "owner").count
row << owners_count
puts "owners_count"
tenants_count = Residents.where(created_at: date, added_by: i, role: "tenant").count
row << tenants_count
members_count = MemeberRelations.where(created_at: date, added_by: i).count
row << members_count
total_people = owners_count + tenants_count + members_count
row << total_people
row_data << row
end
puts row_data
return row_data
end
def generate_csv()
puts "walk away"
row_data = self.collect_data
CSV.open('/home/rajdeep/police-api/daily_report.csv', 'w') do |csv|
row_data.each { |ar| csv << ar }
end
end
end
end
end

If you wish to manage cron tasks from Rails, try whenever gem.
Add it to your Gemfile,
Gemfile
gem 'whenever', require: false
Run initialize task from root of your app
$ bundle exec wheneverize .
This will create an initial config/schedule.rb file for you (as long
as the config folder is already present in your project)
(from the gem docs).
After that in config/schedule.rb set proper parameters of call time. For example
config/schedule.rb
every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
runner "Report.generate_csv"
end
More syntax options of schedule.rb here
UPDATE AFTER COMMENTS
Hope, you're under Rails context yet. Create file in public folder at application root path.
result_file = "#{Rails.root}/public/cards-excluded.csv"
CSV.open(result_file, 'w') do |csv|
row_data.each { |ar| csv << ar }
end
ANOTHER UPDATE LATER
Okay, although this is not relevant to the original question, let's try to solve your problem.
We'll proceed from what you have Rails application, not custom Ruby library.
First, create module at your_rals_app/lib/reports.rb file
module Reports
class Report
class << self
def collect_data
# your current code and line below, explicit return
return row_data
end
def generate_csv
row_data = collect_data # btw unnecessary assignment
CSV.open('/home/rajdeep/police-api/daily_report.csv', 'w') do |csv|
row_data.each { |ar| csv << ar }
end
end
end
end
end
Second, make sure, that you have lib files at autoload path. Check it in you config/application.rb
config.autoload_paths += %W(#{config.root}/lib/*)
Thirdly, use Reports module such way (> means that you're at rails console, rails c)
> Reports::Report.generate_csv

Related

Create timestamped labels on csv files (ruby code)

I am running a transaction download script through Ruby. I was wondering if it is possible to label each .csv it creates with the current date/time the script was run. Below is the end of the script.
CSV.open("transaction_report.csv", "w") do |csv|
csv << header_row
search_results.each do |transaction|
transaction_details_row = header_row.map{ |attribute| transaction.send(attribute) }
csv << transaction_details_row
end
end
Like this?
CSV.open("transaction_report-#{Time.now}.csv", "w") do |csv|
csv << header_row
search_results.each do |transaction|
transaction_details_row = header_row.map{ |attribute| transaction.send(attribute) }
csv << transaction_details_row
end
end
This just appends the time of generation to the file name. For example:
"transaction_report-#{Time.now}.csv"
# => "transaction_report-2019-10-10 16:09:07 +0100.csv"
If you want to avoid spaces in the file name, you can sub these out like so:
"transaction_report-#{Time.now.to_s.gsub(/\s/, '-')}.csv"
# => "transaction_report-2019-10-10-16:09:40-+0100.csv"
Is that what you're after? It sounds right based on the question, though happy to update if you're able to correct me :)

How do I automate running a Ruby script?

I have written a ruby script (code below) to scrape from Deliveroo.co.uk.
Right now I run it manually by going to terminal and typing in 'ruby ....rb'.
How do I automate things so that this script runs automatically every hour?
Also, how do I save the output from each run without overwriting the previous output?
Code is below.. thank you.
require 'open-uri'
require 'nokogiri'
require 'csv'
# Store URL to be scraped
url = "https://deliveroo.co.uk/restaurants/london/maida-vale?postcode=W92DE"
# Parse the page with Nokogiri
page = Nokogiri::HTML(open(url))
# Display output onto the screen
name =[]
page.css('span.list-item-title.restaurant-name').each do |line|
name << line.text.strip
end
category = []
page.css('span.restaurant-detail.detail-cat').each do |line|
category << line.text.strip
end
delivery_time = []
page.css('span.restaurant-detail.detail-time').each do |line|
delivery_time << line.text.strip
end
distance = []
page.css('span.restaurant-detail.detail-distance').each do |line|
distance << line.text.strip
end
status = []
page.css('li.restaurant--details').each do |line|
if line.attr("class").include? "unavailable"
sts = "closed"
else
sts = "open"
end
status << sts
end
# Write data to CSV file
CSV.open("deliveroo.csv", "w") do |file|
file << ["Name", "Category", "Delivery Time", "Distance", "Status"]
name.length.times do |i|
file << [name[i], category[i], delivery_time[i], distance[i], status[i]]
end
end
There's two questions, I'll try to answer them below.
How to run periodically:
What you are looking for is a cronjob, there are many resources out there for creating one.
Look into cron or gems like whenever / clockwork.
Save output between multiple runs: In order to save the output you could just write to a file directly in ruby, very similar to what you are doing right now.
The way you're saving it right now is:
CSV.open("deliveroo.csv", "w") do |file|
The "w" opens the file and overwrites any content present in it, try "a" (append) instead.
CSV.open("deliveroo.csv", "a") do |file|
Read more here about opening files in different modes: File opening mode in Ruby

Ruby, Tempfile, CSV

I have the below resque job that produces a csv file and sends it to a mailer. I want to validate that the csv file has data so I do not email blank files. For some reason, when I write a method outside of the perform method, it will not work. For example, the below code will print invalid when I know the csv file has data on the first line. If I uncomment the line below ensure it works properly, however I want to extract this checking of the file into a separate method. Is this correct?
class ReportJob
#queue = :report_job
def self.perform(application_id, current_user_id)
user = User.find(current_user_id)
client_application = Application.find(client_application_id)
transactions = application.transactions
file = Tempfile.open(["#{Rails.root}/tmp/", ".csv"]) do |csv|
begin
csv_file = CSV.new(csv)
csv_file << ["Application", "Price", "Tax"]
transactions.each do |transaction|
csv_file << [application.name, transaction.price, transaction.tax]
end
ensure
ReportJob.email_report(user.email, csv_file)
#ReportMailer.send_report(user.email, csv_file).deliver
csv_file.close(unlink=true)
end
end
end
def self.email_report(email, csv)
array = csv.to_a
if array[1].blank?
puts "invalid"
else
ReportMailer.send_report(email, csv).deliver
end
end
end
You should invoke your method as such:
ReportJob.email_report(email, csv)
Otherwise, get rid of the self in:
def self.email_report(email, csv)
# your implementation here.
end
and define your method as follows:
def email_report(email, csv)
# your implementation.
end
This is something that we call Class Methods and Instance Methods.

How can I run a "cron script" on heroku by user interaction on Rails?

I have the following script which runs once a day on cron on heroku.
However, I realize that I would like the option for the user to be able to press a button from a web page to initiate this same process.
Is there a way to create a 'subroutine' that either cron can call or from a web request? I don't want to use a separate service that runs jobs.
I've just put a snippet to illustrate.....
letter_todos = Todo.current_date_lte(Date.today).asset_is("Letter").done_date_null
unless letter_todos.blank? #check if there any ToDos
# group by asset_id so that each batch is specific to the asset_id
letter_todos.group_by(&:asset_id).each do |asset_id, letter_todos|
# pdf = Prawn::Document.new(:margin => 100) #format the PDF document
html_file = ''
letter_todos.each do |todo| #loop through all Letter_Todos
contact = Contact.find(todo.contact_id) #get associated contact
letter = Letter.find(todo.asset_id) #get associated Letter
redcloth_contact_letter = RedCloth.new(letter.substituted_message(contact, [])).to_html
html_file = html_file + redcloth_contact_letter
html_file = html_file + "<p style='display: none; page-break-after: always'><center> ... </center> </p>"
end
kit = PDFKit.new(html_file)
kit.stylesheets << "#{RAILS_ROOT}/public/stylesheets/compiled/pdf.css"
file = kit.to_pdf
letter = Letter.find(asset_id)
#OutboundMailer.deliver_pdf_email(file)
kit.to_file("#{RAILS_ROOT}/tmp/PDF-#{letter.title}-#{Date.today}.pdf")
# Create new BatchPrint record
batch = BatchPrint.new
batch.pdf = File.new("#{RAILS_ROOT}/tmp/PDF-#{letter.title}-#{Date.today}.pdf")
I've done this by putting the function in question in a file in lib (lib/tasks_n_stuff.rb, say):
module TasksNStuff
def self.do_something
# ...doing something...
end
end
Then I can call if from a Rake task:
desc 'Make sure we depend on :environment, so we can get to the Railsy stuff...'
task :do_something => :environment do
TasksNStuff.do_something
end
Or from a controller (or anywhere, really):
class WhateverController < ApplicationController
def do_something
TasksNStuff.do_something
end
end
And since you can run a rake task as a cron job (cd /my/rails/root; rake do_something), that should be all you need. Cheers!

How to load the data from a .yml file to database?

There is a table questions, and a data file questions.yml. Assume there is no 'Question' model.
'questions.yml' has some recodes dump from the table.
---
questions_001:
title: ttt1
content: ccc1
questions_002:
title: ttt2
content: ccc2
I want to load the data from the yml file, insert them to database. But I can't use rake db:fixtures:load, because it will treat the content as 'erb' template, which is not want I want
So I want to write another rake task, to load the data manually.
I can read the records by:
File.open("#{RAILS_ROOT}/db/fixtures/#{table_name}.yml", 'r') do |file|
YAML::load(file).each do |record|
# how to insert the record??
end
end
But I don't know how to insert them.
Edit:
I have tried:
Class.new(ActiveRecord::Base).create(record)
and
class Dummy < ActiveRecord::Base {}
Dummy.create(rcord)
But nothing inserted to database
Try this after loading the date from the yml file to records:
class Question < ActiveRecord::Base
# Question model just to import the yml file
end
records.each { |record| Question.create(record) }
You can simply create a model just for importing. You don't need to create the app/models/question.rb. Just write the code above in the script responsible for the importing.
UPDATE:
You can use the following function:
def create_class(class_name, superclass, &block)
klass = Class.new superclass, &block
Object.const_set class_name, klass
end
source
File.open("#{RAILS_ROOT}/db/fixtures/#{table_name}.yml", 'r') do |file|
YAML::load(file).each do |record|
model_name = table_name.singularize.camelize
create_class(model_name, ActiveRecod::Base) do
set_table_name table_name.to_sym
end
Kernel.const_get(model_name).create(record)
end
end
To use the connection directly you can use the following:
ActiveRecord::Base.connection.execute("YOUR SQL CODE")
Got it working thanks to #jigfox 's answer. Had to modify a bit for the full implementation now with Rails 4.
table_names = Dir.glob(Rails.root + 'app/models/**.rb').map { |s| Pathname.new(s).basename.to_s.gsub(/\.rb$/,'') }
table_names.each do |table_name|
table_name = table_name.pluralize
path = "#{Rails.root}/db/fixtures/#{table_name}.yml"
if File.exists?(path)
File.open(path, 'r') do |file|
y = YAML::load(file)
if !y.nil? and y
y.each do |record|
model_name = table_name.singularize.camelize
rec = record[1]
rec.tap { |hs| hs.delete("id") }
Kernel.const_get(model_name).create(rec)
end
end
end
end
end
This loads fixtures into the current RAILS_ENV, which, by default, is development.
$ rake db:fixtures:load

Resources