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
Related
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
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.
I have problem with creating proper inheritance between classes in Ruby On Rails.
Idea:
There are 2 classes: Person and Client. Person is a abstract class and Client inherits Person attribute.
Problem:
My solution doesn't work. I don't know why. How can I correctly implement (prefer CTI) inheritance.
Migrations:
create_persons.rb
class CreatePersons < ActiveRecord::Migration
def self.up
create_table :persons do |t|
t.string :pesel, null: false
t.string :first_name, null: false
t.string :last_name, null: false
t.string :email, null: false
t.date :data_of_birth, null: false
t.string :password_digest, null: false
# required for STI
t.string :type
t.timestamps null: false
end
end
def self.down
drop_table :persons
end
end
create_clients.rb
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
add_foreign_key :persons
t.timestamps null: false
end
end
end
Model Person
class Person < ActiveRecord::Base
self.abstract_class = true
end
Model Client
class Client < Person
end
After db:migrate, when I try Client.create(pesel: "1232",....) there is error:
unknown attribute 'pesel' for Client.
You're getting an error because you've created a clients table in addition to your persons table. If you have a separate table for each class then the only thing that is inherited is the code and not the contents of the database.
Single Table Inheritance (STI) allows you to add a type column and then instances of the parent class and subclasses will be stored in that single table provided the expected table for the subclass isn't present. You've added that type column but you've also created a clients table. This means that ActiveRecord is expecting you to store Client instances in that table instead of the persons - and when it tries to store a Client there it cannot use the field pesel causing your error.
So, if you want to use STI, then you need to remove your clients table and add an client-specific fields to your persons table.
Alternatively, you can keep your clients table and add the fields from persons that you want to also use for clients to the clients table. This wouldn't then be STI but your Client objects would inherit the methods from Person.
I suspect from your inclusion of the type field that you want to get rid of the clients table.
Single Table Inheritance is based upon having a single table to store all the records.
Your problem is that you create a table 'clients' which Rails uses by default for the Client class.
Just rake db:rollback on your last migration and it should look for the superclass table 'people' and work fine.
Edit : Oops, didn't see you mention CTI, this solution only works for STI.
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
frustrating simple problem here. I'm using Rails 3.1 and have the following class:
class Job < ActiveRecord::Base
attr_accessor :active
attr_accessible :roleTagline, :projectTagline, :projectStartDate, :projectDuration, :postedStartDate,
:postedEndDate, :skillsRequired, :skillsPro, :experiencedRequired, :description, :active
scope :is_active, :conditions => {:active => 1}
validates :roleTagline, :presence => true,
:length => { :minimum => 5 }
validates :projectTagline, :presence => true,
:length => { :minimum => 5 }
belongs_to :job_provider
# def active=(act)
# #active = act
# end
end
In my controller, I'm trying to create a Job using mass-assignment (one of the ActiveRecord build helpers), then afterwards set the "active" attribute to 1 and then save the Job. Here's the controller code:
def create
#job_provider = current_user.job_provider
#job = #job_provider.jobs.build(params[:job])
#job.active= 1 # debug logging #job.active here gives an empty string
if #job.save # etc.
I've tried all combinations of removing attr_accessor and writing my own setter instead, but whatever I do I can't seem to find the correct combination to get the attribute to stay on the model. It's not active record I don't think because even before the #job.save the attribute is gone (from using debug logging). I've googled around but can't figure out what I'm doing wrong. Can anyone help please?
Edit: schema.rb from rake:
create_table "jobs", :force => true do |t|
t.string "roleTagline"
t.string "projectTagline"
t.date "projectStartDate"
t.integer "projectDuration"
t.date "postedStartDate"
t.date "postedEndDate"
t.string "skillsRequired"
t.string "skillsPro"
t.string "experiencedRequired"
t.string "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "active"
t.integer "job_provider_id"
end
Another edit:
Ok after even more Googling I finally found the answer to this here:
How can I override the attribute assignment in an active record object?
If you're modifying an ActiveRecord attribute and not a class instance, you need to do:
self[:fieldname] = value
Remove attr_accessor :active from the model. It is causing the value to be saved in an instance variable rather than to the database via the attributes hash. You don't have to write an accessor, because ActiveRecord does that automatically for you, based on the active column.
It's showing up blank in the debug log because it is initialized to nil. If you remove the attr_accessor line and change the active column in the database to NOT NULL DEFAULT 0, it will be initialized to 0. i.e. :null => false, :default => 0 in an ActiveRecord migration or schema.rb.
I think you didn't add the job_provider_id to the att_accessible. Try the following
attr_accessible :roleTagline, :projectTagline, :projectStartDate, :projectDuration, :postedStartDate, :postedEndDate, :skillsRequired, :skillsPro, :experiencedRequired, :description, :active, :job_provider_id