Rails Everyday Action - ruby-on-rails

I need the system to run the following code everyday but I don't know how to do accomplish this.
#user = User.all
date = Date.today
if date.workingday?
#user.each do |user|
if !Bank.where(:user_id => user.id , :created_at => (date.beginning_of_day..date.end_of_day) , :bank_type => 2 ).exists?
banco = Bank.new()
banco.user = user
banco.bank_type = 2
banco.hours = 8
banco.save
end
end
end

The most conventional way is to set this up to be executed with rails runner in a cron job.
There are tools like whenever that make it easier to create these jobs by defining how often they need to be executed in Ruby rather than in the peculiar and sometimes difficult to understand crontab format.
As a note, User.all is a very dangerous thing to do. As the number of users in your system grows, loading them all into memory will eventually blow up your server. You should load them in groups of 100 or so to avoid overloading the memory.
Additionally, that where clause shouldn't be necessary if you've set up proper has_many and belongs_to relationships here. I would expect this could work:
unless (user.bank)
user.create_bank(
bank_type: 2,
hours: 8
)
end
It's not clear how created_at factors in here. Are these assigned daily? If so, that should be something like bank_date as a DATE type column, not date and time. Using the created_at timestamp as part of the relationship is asking for trouble, that should reflect when the record was created, nothing more.

Related

How does one get the "next" record from database sorted by a specific attribute without loading all the records?

Here's the situation:
I have an Event model and I want to add prev / next buttons to a view to get the next event, but sorted by the event start datetime, not the ID/created_at.
So the events are created in the order that start, so I can compare IDs or get the next highest ID or anything like that. E.g. Event ID 2 starts before Event ID 3. So Event.next(3) should return Event ID 2.
At first I was passing the start datetime as a param and getting the next one, but this failed when there were 2 events with the same start. The param start datetime doesn't include microseconds, so what would happen is something like this:
order("start > ?",current_start).first
would keep returning the same event over and over because current_start wouldn't include microseconds, so the current event would technically be > than current_start by 0.000000124 seconds or something like that.
The way I got to work for everything was with a concern like this:
module PrevNext
extend ActiveSupport::Concern
module ClassMethods
def next(id)
find_by(id: chron_ids[current_index(id)+1])
end
def prev(id)
find_by(id: chron_ids[current_index(id)-1])
end
def chron_ids
#chron_ids ||= order("#{order_by_attr} ASC").ids
end
def current_index(id)
chron_ids.find_index(id)
end
def order_by_attr
#order_by_attr ||= 'created_at'
end
end
end
Model:
class Event < ActiveRecord::Base
...
include PrevNext
def self.order_by_attr
#order_by_attr ||= "start_datetime"
end
...
end
I know pulling all the IDs into an array is bad and dumb* but i don't know how to
Get a list of the records in the order I want
Jump to a specific record in that list (current event)
and then get the next record
...all in one ActiveRecord query. (Using Rails 4 w/ PostgreSQL)
*This table will likely never have more than 10k records, so it's not catastrophically bad and dumb.
The best I could manage was to pull out only the IDs in order and then memoize them.
Ideally, i'd like to do this by just passing the Event ID, rather than a start date params, since it's passed via GET param, so the less URL encoding and decoding the better.
There has to be a better way to do this. I posted it on Reddit as well, but the only suggested response didn't actually work.
Reddit Link
Any help or insight is appreciated. Thanks!
You can get the next n records by using the SQL OFFSET keyword:
china = Country.order(:population).first
india = City.order(:population).offset(1).take
# SELECT * FROM countries ORDER BY population LIMIT 1 OFFSET 1
Which is how pagination for example often is done:
#countries = Country.order(:population).limit(50)
#countries = scope.offset( params[:page].to_i * 50 ) if params[:page]
Another way to do this is by using would be query cursors. However ActiveRecord does not support this and it building a generally reusable solution would be quite a task and may not be very useful in the end.

rails increment non-primary ID column in after create callback

I'm trying to create a record and increment two non primary id columns in an after create callback with a integer and a date. The first column needs to be auto incremented with an integer from 1 to given number. The second column needs to be incremented with a date from the start date specified and by either 7, 14, or 30 days which is also specified. I tried creating the first record with the first value then incrementing from there but all records just have the same integer or date saved.
Here's the code
def create_positions
#slots = (self.slots - 1)
#payout_date = (self.start_date)
#position = Position.create(:susu_id => self.id, :user_id => 2, :position_number => 1, :pay_in => self.contribution, :payout_date => (#payout_date + self.frequency_in_days.days))
#positions= self.positions.map { |p| p["position_number"] }.last.to_i
#position_number = (#positions += 1)
#dates = self.positions.map { |d| d["payout_date"] }.last
#add_date = (#dates + self.frequency_in_days.days)
#all_positions = #slots.times {Position.create(:susu_id => self.id, :user_id => 2, :position_number => #position_number, :pay_in => self.contribution, :payout_date => #add_date) }
end
What i get is: 1 2 2 2 and 1/2/14 1/9/14 1/9/14 1/9/14
instead of: 1 2 3 4 and 1/2/14 1/9/14 1/16/14 1/23/14
My lack of reputation prohibits me from commenting on your original post, so I'll just have to do my best to answer your question without asking for clarification (for I don't fully understand your explanation of what you're trying to do).
It sounds like you are trying to create four Position instances, each subsequent position having a position_number that is one greater than the previous one and having a payout_date that is 7 days later than the previous one.
If this is what you're trying to do, please consider the following:
# in the top of your susu model:
after_create :create_positions
# later in your susu model
def create_positions
4.times do |n|
Position.create(susu: self,
user_id: 2,
position_number: n,
pay_in: contribution,
payout_date: start_date + n * frequency_in_days.days)
end
end
You have a lot of logic in your version of this method, so let me know if I have missed something that you need.
And finally, let me point out a few things about my version of this code:
It uses Ruby 1.9 hash syntax ("susu: self" instead of ":susu => self")
It removes unnecessary calls to self, which is considered bad ruby style
It still has hardcoded user_id: 2, which seems like you're going to need to change at some point
Doing this in an after_create hook will be in the same transaction as the original object being created. This means that if a Position cannot be created for whatever reason (e.g., a database-level unique key or a validates_uniqueness_of on the Position model failing), it will roll back the creation of the original object. If you don't like that behavior you can use the after_commit callback.
Cheers!

Rails: build for difference between relationships

A doc has many articles and can have many edits.
I want to build an edit for each article up to the total number of #doc.articles. This code works with the first build (i.e., when no edits yet exist).
def editing
#doc = Doc.find(params[:id])
unbuilt = #doc.articles - #doc.edits
unbuilt.reverse.each do |article|
#doc.edits.build(:body => article.body, :article_id => article.id, :doc_id => #doc.id)
end
end
But when edits already exist it'll keep those edits and still build for the #doc.articles total, ending up with too many edits and some duplicates if only one article was changed.
I want to put some condition against :article_id which exists in both edits and articles in to say (in pseudocode):
unbuilt = #doc.articles - #doc.edits
unbuilt.where('article_id not in (?)', #doc.edits).reverse.each do |article|
#doc.edits.build(...)
end
Any help would be excellent! Thank-you so much.
You are doing something weird here:
unbuilt = #doc.articles - #doc.edits
You probably want this instead
unbuilt = #doc.articles - #doc.edits.map(&:article)
This works if #doc.articles and #doc.edits are small collections, otherwise a SQL solution would be preferred.
-- EDIT: added explanation --
this piece of Ruby
#doc.edits.map(&:article)
is equivalent to
#doc.edits.map do |edit| edit.article end
the previous one is much more compact and exploits a feature introduced in ruby 1.9
It basically takes a symbol (:article), calls on it the 'to_proc' method (it does this by using the '&' character). You can think of the 'to_proc' method as something very similar to this:
def to_proc
proc { |object| object.send(self) }
end
In ruby, blocks and procs are generally equivalent (kindof), so this works!

Change default_timezone of active record

In rails, I have the below config for activerecord at first.
config.active_record.default_timezone = :utc
Now, I want to use the local timezone, so I changed it to:
config.active_record.default_timezone = :local
The problem is, I need to shift all the existing data in the date/datetime column to the local timezone.
What is the best way to achieve this?
Why I have to do this is because I have to do aggregation on the local timezone, for example, :group => 'DATE(created_at)', GROUP BY DATE(created_at) will be based on the UTC, but I want to aggregate with one day in local timezone.
I knew how to write a migration file to migrate a certain datetime column. But there are a lot of such column, so I'm seeking for a better solution.
This is dangerous, but here is what I'd do in the migration:
class MigrateDateTimesFromUTCToLocal < ActiveRecord::Migration
def self.up
# Eager load the application, in order to find all the models
# Check your application.rb's load_paths is minimal and doesn't do anything adverse
Rails.application.eager_load!
# Now all the models are loaded. Let's loop through them
# But first, Rails can have multiple models inheriting the same table
# Let's get the unique tables
uniq_models = ActiveRecord::Base.models.uniq_by{ |model| model.table_name }
begin
# Now let's loop
uniq_models.each do |model|
# Since we may be in the middle of many migrations,
# Let's refresh the latest schema for that model
model.reset_column_information
# Filter only the date/time columns
datetime_columns = model.columns.select{ |column| [ :datetime, :date, :time].include? column.type }
# Process them
# Remember *not* to loop through model.all.each, or something like that
# Use plain SQL, since the migrations for many columns in that model may not have run yet
datetime_columns.each do |column|
execute <<-SQL
UPDATE #{model.table_name} SET #{column.name} = /* DB-specific date/time conversion */
SQL
end
rescue
# Probably time to think about your rescue strategy
# If you have tested the code properly in Test/Staging environments
# Then it should run fine in Production
# So if an exception happens, better re-throw it and handle it manually
end
end
end
end
My first advice is to strongly encourage you to not do this. You are opening yourself up to a world of hurt. That said, here is what you want:
class ShootMyFutureSelfInTheFootMigration
def up
Walrus.find_each do |walrus|
married_at_utc = walrus.married_at
walrus.update_column(:married_at, married_at_utc.in_time_zone)
end
end
def down
Walrus.find_each do |walrus|
married_at_local = walrus.married_at
walrus.update_column(:married_at, married_at_local.utc)
end
end
end
You may pass in your preferred timezone into DateTime#in_time_zone, like so:
central_time_zone = ActiveSupport::TimeZone.new("Central Time (US & Canada)")
walrus.update_column(:married_at, married_at_utc.in_time_zone(central_time_zone))
Or you can leave it and Rails will use your current timezone. Note that this isn't where you are, it is where your server is. So if you have users in Iceland and Shanghai, but your server is in California, every single 'local' time zone will be US Pacific Time.
Must you change the data in the database? Can you instead display the dates in local time zone. Does this help: Convert UTC to local time in Rails 3
Like a lot of other people said, you probably don't want to do this.
You can convert the time to a different zone before grouping, all in the database. For example, with postgres, converting to Mountain Standard Time:
SELECT COUNT(id), DATE(created_at AT TIME ZONE 'MST') AS created_at_in_mst
FROM users GROUP BY created_at_in_mst;

How to update a single attribute without touching the updated_at attribute?

How can I achieve this?
tried to create 2 methods, called
def disable_timestamps
ActiveRecord::Base.record_timestamps = false
end
def enable_timestamps
ActiveRecord::Base.record_timestamps = true
end
and the update method itself:
def increment_pagehit
update_attribute(:pagehit, pagehit+1)
end
turn timestamps on and off using callbacks like:
before_update :disable_timestamps, :only => :increment_pagehit
after_update :enable_timestamps, :only => :increment_pagehit
but it's not updating anything, even the desired attribute (pagehit).
Any advice? I don't want to have to create another table just to count the pagehits.
As an alternative to update_attribute, In Rails 3.1+ you can use update_column.
update_attribute skips validations, but will touch updated_at and execute callbacks.
update_column skips validations, does not touch updated_at, and does not execute callbacks.
Thus, update_column is a great choice if you don't want to affect updated_at and don't need callbacks.
See http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html for more information.
Also note that update_column will update the value of the attribute in the in-memory model and it won't be marked as dirty. For example:
p = Person.new(:name => "Nathan")
p.save
p.update_column(:name, "Andrew")
p.name == "Andrew" # True
p.name_changed? # False
If all you're wanting to do is increment a counter, I'd use the increment_counter method instead:
ModelName.increment_counter :pagehit, id
Is there a way to avoid automatically updating Rails timestamp fields?
Or closer to your question:
http://blog.bigbinary.com/2009/01/21/override-automatic-timestamp-in-activerecord-rails.html
it is not a good idea to do this:
self.class.update_all({ pagehit: pagehit+1 }, { id: id })
it should be
self.class.update_all("pagehit = pagehit + 1", { id: id })
the reason is if two requests are parallel, on the first version both will update the pagehits with the same number, as it uses the number saved in the Ruby memory. The second option uses the sql server to increase the number by 1, in case two of these queries come at the same time, the server will process them one after the other, and will end up with the correct number of pagehits.
To avoid Monkeypatchingtroubles you could also use ModelName.update_all for this purpose:
def increment_pagehit
self.class.update_all({ pagehit: pagehit+1 }, { id: id })
end
This also does not touch the timestamps on the record.
You also have decrement and increment (and their bang versions) which do not alter updated_at, do not go trigger validation callbacks and are obviously handy for counters / integers.
If precision is not really that important, and you don't expect the code to run many times, you can try altering the saved in the database updated_at value, like so:
u = User.first
u.name = "Alex 2" # make some changes...
u.updated_at = u.updated_at + 0.000001.second # alter updated_at
u.save
so that Rails will actually try to save the same value, and not replace it with Time.now.

Resources