How to make a trigger with Rails 7 and PostgreSQL - ruby-on-rails

I'm using PostgreSQL with Rails in API mode and I have a row called expired with data type Date and I want to make a trigger that identifies if the expired date is equal to the current date and change my is_expired column from false to true, but I don't know where to start.
I've read a bit of Rails documentation or some libraries like hairtrigger and it seems a bit confusing.
this is my table:
class CreateRifs < ActiveRecord::Migration[7.0]
def change
create_table :rifs do |t|
t.string :name
t.datetime :rif_date
t.string :award_with_sign
t.string :award_no_sign
t.string :plate
t.integer :year
t.float :price
t.integer :numbers
t.integer :with_signs
t.date :expired
t.boolean :is_expired, default: false
t.timestamps
end
end
end

Do you have a specific reason to use a database column for this? Because you could easily write this method on the model:
def expired? # This is actually an override, see next paragraph
expired <= Date.today
end
Or alternatively, if the expired column only gets populated with a past date after it actually has expired (and doesn't, e.g., represent a future expiry), don't write a method at all. Rails automatically provides you with a predicate for every column: a column? method that returns true if column is populated.

You don't need a trigger for this, maybe a scope to only get expired vs not expired records.
class Rif < ApplicationRecord
scope :expired, -> { where('expired < NOW()') }
end
You can then use the scope later on
expired_rifs = Rif.expired

Related

Polymorphic belongs_to association with dynamic class (taken from other relationship)

I've got a Rails app where Users are able to keep track of airing shows and episodes.
To simplify the process of keeping track of (not yet) watched shows, users are able to synchronize their account with other services. They can, in their user settings page, choose which service they want to synchronize with.
To synchronize, I load their profile from the other service, and then run it through an algorithm which detects changes from the last synchronization, and updates the DB accordingly. In order to store the last synchronization status, for each Show ID, I create a "UsersSyncIdStatus" object which stores the show ID, as well as the current status for that show in the synchronized service.
Note that the services do not use the same Show IDs as my website, which means that I have a table which I can use to "convert" from their show IDs to my show IDs. Since the information each service provides is different, they must be stored in different tables.
Right now, this is (a simplified version of) how the DB schema is set up:
create_table "service1_ids", force: :cascade do |t|
t.integer "service_id", null: false
t.integer "show_id", null: false
[...]
end
create_table "service2_ids", force: :cascade do |t|
t.integer "service_id", null: false
t.integer "show_id", null: false
[...]
end
create_table "users_sync_id_statuses", force: :cascade do |t|
t.integer "user_id"
t.integer "service_id", null: false
t.integer "sync_status", default: 0, null: false
t.datetime "sync_date", null: false
[...]
end
create_table "users", force: :cascade do |t|
[...]
t.datetime "synced_at"
t.boolean "sync_enabled", default: false, null: false
t.integer "sync_method", default: 0, null: false
[...]
end
In particular, users.sync_method is an enum which stores the service the user has selected for synchronization:
SYNC_METHODS = {
0 => {
symbol: :service1,
name: 'Service1',
model_name: 'Service1Id',
show_scope: :service1_ids
}
1 => {
symbol: :service2,
name: 'Service2',
model_name: 'Service2Id',
show_scope: :service2_ids
}
}
This means I can easily know the model name of the IDs of a specific user by just doing SyncHelper::SYNC_METHODS[current_user.sync_method][:model_name].
Now, the question is, how can I have a relationship between "users_sync_id_statuses" and "serviceN_ids"? In order to know which class the "service_id" column corresponds to, I have to 'ask' the user model.
I currently have it implemented as a method:
class User
def sync_method_hash
SyncHelper::SYNC_METHODS[self.sync_method]
end
def sync_method_model
self.sync_method_hash[:model_name].constantize
end
end
class UsersSyncIdStatus
def service_id_obj
self.user.sync_method_model.where(service_id: self.service_id).first
end
end
However, UsersSyncIdStatus.service_id_obj is a method, not a relationship, which means I cannot do all the fancy stuff a relationship allows. For example, I cannot easily grab the UsersSyncIdStatus for a specific user and show ID:
current_user.sync_id_statuses.where(service_id_obj: {show_id: 123}).first
I could turn it into a polymorphic relationship, but I really don't want to have to add a text column to contain the class name, when it is a "constant" from the point of view of each user (for a user to switch synchronization service, all UsersSyncIdStatuses for that user are destroyed, so a user never has more than 1 service type in their UsersSyncIdStatuses).
Any ideas? Thank you in advance!
I don't think vanilla Rails 5 supports what I want to do, someone please correct me if I'm wrong.
Still, after some research into how Rails implements polymorphic relationships, I was able to relatively easily monkey-patch Rails 5 to add this functionality:
config/initializers/belongs_to_polymorphic_type_send.rb:
# Modified from: rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
module ActiveRecord
# = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
def klass
type = owner.send(reflection.foreign_type)
type.presence && (type.respond_to?(:constantize) ? type.constantize : type)
end
end
end
end
app/models/users_sync_id_status.rb:
class UsersSyncIdStatus
belongs_to :service_id_obj, polymorphic: true, foreign_key: :service_id, primary_key: :service_id
def service_id_obj_type
self.user.sync_method_model
end
end
With this monkey-patch, belongs_to polymorphic associations do not assume that the type field is a varchar column, but instead call it as a method on the object. This means you can very easily add your own dynamic behavior, without breaking any old behavior (AFAIK, didn't do intensive testing on that).
For my specific use-case, I have the sync_id_obj_type method query the user object for the class that should be used in the polymorphic association.

How to modify the params that rails use to pull object from database

Sorry about the title, it's really hard to explain but I'll try my best to make it as clear as possible.
So, I have this app that I am building sort of 'Social Shift Trading Network'. So in this app I have a calendar(fullcalendar-rails) that I use, and I generate shifts using a model but I realise that the id generated automatically by the database tend to not be useful when there are a lot of deletion of object or changes because the index is not being reset. So, I thought that I would put a shift_id column in my Shift model and generate id with SecureRandom.urlsafe_base64(8).
But how do I set it as a primary key so that when I edit or call show on it for it to use shift_id as params?
I tried set :id => false, and set :primary => :shift_id but still no result. I believe because my route format is "/shifts/:id/edit(.:format)" formatted to pull :id that it does not work.
Thank for you for any help in advance.
class CreateShifts < ActiveRecord::Migration
def change
create_table :shifts, {:id => false, :primary_key => :shift_id} do |t|
t.string :position
t.datetime :date
t.datetime :start_time
t.datetime :finish_time
t.integer :original_owner
t.integer :current_owner
t.string :shift_id
t.string :shift_posted, :default => "Not Posted"
t.timestamps
end
end
If what I understood is correct, all you want is that the URL becomes something like this
/shifts/:some_random_hash/edit
To do that you don't need to mess your database like that, just generate that normal table, and add that :shift_id field, then tell the model which field to use for the url.
class Shift < ActiveRecord::Base
to_param
shift_id
end
end
This way when you use url_for or shift_path the model will use the :shift_id instead to generate that URL, but internally it would use the auto increment id

rails 4: problems with inheritance

I probably didn't understand STI or whatever inheritance of rails :)
I need to implement a design where I have a form; on upload I submit, but if the user is not logged in, she needs to login via openid (only access supported). Thus this implies a redirect, and the best thing I could come up with so far is to temporarily save the data, and on successful login actually create the real object. I wanted to have two separate tables for the objects; the Site object is the one to be created, the Sitetmp is the temporary store, and it has an additional field called nonce; (on successful login, the nonce will be compared, and if ok, the Sitetmp instance deleted and a new Site one created)
The DB:
#schema.rb
create_table "sites", force: true do |t|
t.string "name"
t.date "date"
t.text "description"
t.float "lat"
t.float "lon"
t.date "updated"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "sitetmps", force: true do |t|
t.string "nonce"
t.string "name"
t.date "date"
t.text "description"
end
The Subclass:
#app/models/sitetmp.rb
class Sitetmp < Site
attr_accessor :nonce
end
The Superclass:
#app/models/site.rb
class Site < ActiveRecord::Base
validates :name, uniqueness: true, :presence => true,
:length => { :minimum => 5 }
validates :date, :presence => true
has_many :images, :inverse_of => :site
end
It seemed to be all set, but when I actually want to access the temporary object on successful login, it tells me
SQLite3::SQLException: no such column: sites.nonce: SELECT "sites".* FROM "sites" WHERE "sites"."nonce" = '059253928646750523787961570357' LIMIT 1
The code in the controller causing this is:
if nonce
#tmp = Sitetmp.find_by nonce: nonce
Clearly I am trying to access the Sitetmp instance by its nonce attribute, but rails is resolving to actually access the Site class - which doesn't have a nonce attribute.
What am I doing wrong? How do I correctly find the Sitetmp object by nonce in order to create a valid Site object from it?
As I understand it, ActiveRecord::Base has a table_name method. If you inherit from ActiveRecord::Base, the subclass (Site), through some rails magic, has the table_name set to the underscored lowercased version of itself.
Then inheriting from the Site class, Sitetmp does not get the advantage of having the table_name set.
You may be able to fix this by setting this:
#app/models/sitetmp.rb
class Sitetmp < Site
# Set custom table name
table_name "sitetmp"
attr_accessor :nonce
end
I don't know if this would have an impact on the table_name of the Site class, so you may just have to create a standalone Sitetmp model to work from.
Alternatively, you could load the values into the session - see here for more

Rails: RSPEC test for updating active record entry is failing

I have two models: Draft and Pick. I want the Draft's ActiveRecord column current_pick to increase by 1 after a Pick is created.
Inside Pick, I have a method that increase draft.current_pick by 1:
class Pick < ActiveRecord::Base
belongs_to :draft
after_save :advance_draft
def advance_draft
draft.updraft
end
Inside draft, updraft is:
def updraft
self.current_pick += 1
end
My test to ensure the current_pick is being increased by one is:
it 'should run the advance_draft method after creating' do
team1 = FactoryGirl.create(:team)
team2 = FactoryGirl.create(:team_two)
cam = FactoryGirl.create(:player)
draft = FactoryGirl.create(:two_team_draft)
pick = Pick.create(:player_id => cam.id, :team_id => draft.team_at(draft.current_pick).id, :draft_id => draft.id, :draft_position => draft.current_pick)
draft.draft_position.should eq 2
end
The pick and draft are being created in the test but the updraft method is not being called on the correct draft because the pick.draft.draft_position remains at 1 after the pick has been created. (it should increase to 2).
Here is my schema:
create_table "drafts", force: true do |t|
t.integer "draft_position"
t.integer "number_of_teams"
t.integer "PPTD"
t.integer "PPR"
t.integer "current_pick", default: 1
end
create_table "picks", force: true do |t|
t.integer "player_id"
t.integer "team_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "draft_id"
t.integer "draft_position"
end
My question is, how do I properly increase the pick.draft.current_pick inside my test?
I would do 2 things, use increment! when updating the count to increment the value and save the record, and reload the object you're looking at, since its database representation has changed since it was created.
def updraft
increment!(:current_pick)
end
This will update the current_pick and save the object in one shot.
it 'should run the advance_draft method after creating' do
# ... setup
draft.reload.draft_position.should eq 2
end
Now, instead of using the instance of draft that was created before the modification occurred, it's using a version current with the database.

Ruby on Rails - Monthly top vote getter

I need some advice on a voting system in rails that recognizes the top vote getter on a monthly basis. I have a system that works but being new to rails, I'm sure there are more efficient methods available. Below is a simplified version of my current setup(controller code omitted):
class Charity < ActiveRecord::Base
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :charity
end
My schema is as follows:
ActiveRecord::Schema.define(:version => 20130310015627) do
create_table "charities", :force => true do |t|
t.string "name"
t.text "description"
t.date "last_win"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "votes", :force => true do |t|
t.integer "charity_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
I'll be using the 'whenever' gem to run a cron job to determine the monthly winner and update the 'last_win' column of the charities table.
The following code is where I'm questioning my efficiency:
vote_counts = Vote.count(:group => "charity_id")
most_votes = vote_counts.values.max
winning_ids = vote_counts.map{|k,v| v == most_votes ? k :nil }.compact
charities = Charity.find(winning_ids)
charities.each {|charity| charity.update_attributes(:last_win => Date.today)}
I'm sure there are many ways to do this better and would appreciate some suggestions. If you have suggestions on better ways to set up the votes table / associations, that would be appreciated too.
Thanks in advance,
CRS
Something like this:
If there was only one winner, this would work I think
winner_id = Vote.group(:charity_id).order("count(*) desc").pluck(:charity_id).first
Charity.find(winner)id).update_attribute!(:last_win => Date.today)
You could modify it for ties:
most_votes = Vote.group(:charity_id).order("count(*) desc").count.first[1]
winners = Vote.group(:charity_id).having("count(*) = ?", most_votes).pluck(:charity_id)
Charity.where(:id => winners).update_all(:last_win => Date.today)
Make sure everything is indexed correctly in your database,
You can probably streamline it more, but the SQL is going to get more complicated.
The last two lines could be:
Charity.where(id:winning_ids).update_all(last_win:Date.today)
Which would translate into a single SQL update command, instead of issuing an update command for each winning charity.
The first part where you identify the winning charities looks okay, and since you're running it as a cron job you probably don't care if it takes a few minutes.
However, if you'd like to show the values in real time, you could add an after_create hook on Vote to update a counter for its owner charity (possibly in another table):
class Vote < ActiveRecord::Base
belongs_to :charity
after_create :increment_vote_count
CharityVote.where(year:Time.now.year, month:Time.now.month,
charity_id:self.charity_id).first_or_create.increment!(:counter)
end

Resources