Activerecord relation not nullifying dependent despite being asked to do so - ruby

I'm getting the following error:
PG::ForeignKeyViolation: ERROR: update or delete on table "sites" violates foreign key constraint "fk_rails_b1cb5ea385" on table "domains" DETAIL: Key (id)=(1) is still referenced from table "domains". : DELETE FROM "sites" WHERE "sites"."id" = $1
This is because there is a Domain record that has a reference to the site being deleted, I know this because manually removing the site_id causes the error to go away (of course this is not the way to do this, this was done for checking purposes only).
However as can be seen in the model:
class Site < ApplicationRecord
enum environment: %i{development staging production}
belongs_to :project
belongs_to :client
has_one :domain, dependent: :nullify
has_many :servers, through: :domain
end
I am indeed asking active record to nullify the domain ( though I am considering outright destroying it, that isn't relevant to this issue).
This association is also used in Server:
class Server < ApplicationRecord
before_save :set_domains, if: :sites_id_changed?
has_many :domains, dependent: :nullify
has_many :sites, through: :domains
def clients
self.sites.map(&:client).flatten
end
def set_domains
domains = get_domains Site.where(id: self.site_ids).all
domains += get_domains domains,:domains
self.domains = domainsprimary_domains
end
private
def get_domains(object,meth=:domain)
objects.first.blank? ? [] : objects.map(&meth).flatten
end
end
and Domain:
class Domain < ApplicationRecord
alias_attribute :aliases, :domains
alias_attribute :alias_of, :domain
has_many :domains, dependent: :nullify
belongs_to :domain, optional: true
belongs_to :site, optional: true
belongs_to :server, optional: true
def alias?
!self.alias_of.blank?
end
accepts_nested_attributes_for :domains, allow_destroy: true
end
Why despite being asked to so do is active record not nullifying the reference to site table in the domain table despite being asked (seemingly at least) to do so?

I have run into something similar in the past. You are correct that this is a situation where you are running into a database-level foreign key constraint.
If you're using PG, for example, here are some helpful docs with more information on the constraint in general.
As far as solving your actual issue, the SO response that helped me overcome this error and get things working again I found here.
Let me know if that works/helps! :)

Related

Mix Polymorphic associations with STI or MTI in Ruby on Rails

I have a problem or dilemma after implementing polymorphic association following this: All the code is located here,
I have implemented this model:
Let's suppose that I also need the subscription to magazines. It will also be something similar to the other two
class Magazines < ApplicationRecord
has_many :subscriptions, as: :subscribable
end
class User < ApplicationRecord
has_many :subscriptions
has_many :podcasts, through: :subscriptions, source: :subscribable, source_type: 'Podcast'
has_many :newspapers, through: :subscriptions, source: :subscribable, source_type: 'Newspaper'
has_many :magazines, through: :subscriptions, source: :subscribable, source_type: 'Newspaper'
end
class Subscription < ApplicationRecord
belongs_to :subscribable, polymorphic: true
belongs_to :user
end
It works well, the problem now is ok I can handle like three types of subscription
MagazineSubscription, PodcastSubscription and NewspaperSubscription. The three have the same attributes and same behaviour, but belongs to different model. What happens If after doing that I need some kind of MTI or STI with the subscription classes. i.e. the MagazineSubscription have different behaviour and maybe other attributes. There is an easy way on this to accomplish this new requirement like creating a Subscription class that handles all that the polymorphic association and the other models:
class Subscription < ActiveRecord::Base
self.inheritance_column = :sti_subscription
belongs_to :subscribable, polymorphic: true
belongs_to :user
def _type=(sType)
sti_subscrition = sType.to_s.classify.constantize.base_class.to_s + "Subscription"
super(sType.to_s.classify.constantize.base_class.to_s)
end
end
class MagazineSubscription < Subscription
# new behaviour here
end
or I must follow something similar to this with the Subscription class handling two polymorphic relations, with the subscribable and his descendants
So my question is when I have used polymorphic association is an easy way to use that to set STI or MTI, or I need to make a new approach

Manually manage join table association in activeRecord

I have three tables, server, domain, site, I'm trying to make it so it works like this:
You create a site, then a primary domain and aliases and assign the primary domain to a site (by selecting a site via drop down box within domain page).
Then in the servers, you select a site and it auto assigns all domains for that site (including aliases) to that server.
The Domains table is also used as a through table for both the site and server tables
However at the moment, when you select a site and then save it it auto-generates the relation rather then using the one's that are already there.
Also for some reason activeRecord won't let me assign the domains to the server either, no matter how I try it:
server.domains = domains
server.domains_id = domains.map(:&id)
server[:domains_id] = domains.map(:&id)
server.assign_attribute(:domains, domains)
server.assign_attribute(:domains_id, domains.map(:&id)
domains_id on the server remains nil despite not erroring (it saves successfully, just domains_id is nil)
Ultimately, what I would like to know is how to manually manage the join table and also why won't rails let me assign values to the attribute?
server.rb:
class Server < ApplicationRecord
before_save :set_domains#, if: :sites_id_changed?
has_many :domains, dependent: :nullify
has_many :sites, through: :domains
has_many :projects, through: :sites
has_many :clients, through: :projects
def set_domains
domains = get_domains Site.where(id: self.site_ids).all
domains += get_domains domains,:domains
domains.each do |domain|
domain.server = self
end
self.domains = domains
end
private
def get_domains(objects,meth=:domain)
objects.first.blank? ? [] : objects.map(&meth).flatten
end
end
domain.rb:
class Domain < ApplicationRecord
alias_attribute :aliases, :domains
alias_attribute :alias_of, :domain
has_many :domains, dependent: :nullify
belongs_to :domain, optional: true
belongs_to :site, optional: true
belongs_to :server, optional: true
def alias?
!self.alias_of.blank?
end
accepts_nested_attributes_for :domains, allow_destroy: true
end
site.rb:
class Site < ApplicationRecord
enum environment: %i{development staging production}
before_save :set_server
belongs_to :project, optional: true
belongs_to :client, optional: true
has_one :domain, dependent: :nullify
has_many :servers, through: :domain
def set_server
self.server = self.domain.server
end
end

Configuring the proper join column in Rails Admin

I have two models, which associate with each other through a has_and_belongs_to_many relationship.
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands
end
Assume both tables have an id field, and that stage has a stage_name field.
They're related to each other through a table called bands_stages, with a schema that looks similar to this:
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
My intention is to use Rails Admin to allow us to modify certain fields on the Stage, but every time that runs, I get an SQL error doing so:
column stages.id does not exist
It seems that Rails Admin is picking the wrong column by default to join on. How would I inform Rails Admin that I want it to join on a column that actually exists in my join table?
Note that I can't actually make use of the ID in the stages table. The intention is that only ten stages exist at any given time, denoted by their stage number, but every band can visit each stage. Since an ID would automatically increment, it seems to be safer and more explicit to its intent to leverage the more concrete :stage_number field instead.
I'm sure that it's not a problem of rails admin but habtm association.
To make habtm use the right column in sql primary key must be specified for stage model and foreign key for association.
And it is the only way to make it works right.
class Stage < ActiveRecord::Base
self.primary_key = "stage_number"
has_and_belongs_to_many :bands, foreign_key: :stage_number
end
But I think the best way is to use joint model and has_many/belongs_to because for has_many/belongs_to it's possible to set any column to be used as primary key via :primary_key option.
class BandStageLink < ActiveRecord::Base
self.table_name = "bands_stages"
belongs_to :band
belongs_to :stage, foreign_key: :stage_number, primary_key: :stage_number
end
class Band < ActiveRecord::Base
has_many :band_stage_links
has_many :stages, through: :band_stage_links, foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_many :band_stage_links, primary_key: :stage_number, foreign_key: :stage_number
has_many :bands, through: :band_stage_links
end
Update: Note that in this case there is still no need to specify any primary keys for stage table. For instance my migration is:
class CreateStageBandTables < ActiveRecord::Migration
def change
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
create_table :bands do |t|
t.string :name
end
create_table :stages, id: false do |t|
t.integer :stage_number
t.string :name
end
end
end
I tested both cases for rails 4.2.5 and everything works just fine.
Edit - I did mis-understand the primary key bit, I think the desire was to tell Rails to use different attribute as PK, which should be less problematic than re-purposing the auto-increment-by-default PK ID. In that case, the Stage model should include self.primary_key = "stage_number", and the rest of the details at the bottom of this answer relating to HABTM alongside that. Of course has-many-through would still be my preferred solution here.
I think there's a bigger problem with the models and approach, than Rails Admin.
If I understand what you're trying to do, then you'd also need to turn off auto-increment for the primary key in stages table, to hold arbitrary numbers (representing stage numbers) as primary key IDs. It could end badly very quickly, so I'd advise against it.
If the data is genuinely static (10 stages ever), you could even keep it as a constant in the Band model and scrap Stage completely (unless there's more there), e.g.
class Band < ActiveRecord::Base
POSSIBLE_STAGES = [1, 2, ...]
validates :stage, inclusion: { in: POSSIBLE_STAGES, message: "%{value} is not a stage we know of!" }
end
For a table-based approach, I would suggest has-many-through, it'll save you a lot of pain in the future (even if you don't need additional attributes on the join table, things like nested forms are a little easier to work with than in HABTM). Something like this:
class Band < ActiveRecord::Base
has_many :events
has_many :stages, through :events
# band details go into this model
end
class Event < ActiveRecord::Base
belongs_to :band
belongs_to :stage
# you could later add attributes here, such as date/time of event, used_capacity, attendee rating, and
# even validations such as no more than X bands on any given stage at the same time etc.
end
class Stage < ActiveRecord::Base
has_many :events
has_many :bands, through :events
# stage number/details go into this model
end
The migration for that could look something like this:
create_table :bands do |t|
t.string :bandname
# whatever else
end
create_table :events do |t|
t.belongs_to :band
t.belongs_to :stage
# you could add attributes here as well, e.g. t.integer :used_capacity
end
create_table :stages do |t|
t.integer :number
t.integer :total_capacity
# whatever else
end
As you can see primary key IDs are not touched here at all, and I would always avoid storing business data in Rails' and databases' plumbing of any sort (which is what I consider IDs to be, they're there to ensure relation/integrity of the data in a relational database, as well as nice and consistent mapping to ActiveRecord - all business data should be beside that, in actual attributes, not plumbing used to connect models).
If you still want HABTM and re-purposing of primary ID, then I think Stage should include a foreign_key statement to "advertise" itself to the bands_stages join table as having a custom key name (in bands_stages only), while keeping the association_foreign_key on the Band end to show what you want to query in the join table to reach the other side. The stages table would still utilise id though as its primary key, you'd just want to turn off auto-increment with something like t.integer :id, :options => 'PRIMARY KEY' (might be dependent on the database flavour - and again, I would advise against this).
Your models would look like this:
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: "stage_number"
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: "stage_number"
end
The connection between bands and bands_stages would be bands.id = bands_stages.band_id, for which many bands_stages.stage_number would be found, and each would be connected to stage via bands_stages.stage_number = stages.id (where stages.id has been re-purposed to represent business data at a likely future peril).
Change the association_foreign_key value to be a string instead of symbol.
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: 'stage_number'
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: 'stage_number'
end

ruby remove common element collections

I have three classes: User, Subscription and Plan. I want to load all of the Plans that the User doesn't have. What's the best way to do it in Rails?
I have two collections: current_user.subscriptions and Plan.where(active: true)
And i am using mongoid
def dashboard
#plans = Plan.where(active: true)#.each { #plans.delete_if has_current_user_plan subscription.title }
end
def has_current_user_plan(name)
current_user.subscriptions.where(title: name, active: true).exists?
end
class User
has_many :subscriptions
class Subscription
belongs_to :plan
belongs_to :user
class Plan
has_many :subscriptions
AR:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :plans, through: :subscriptions # !!!
end
Plan.where(active: true).where.not(id: current_user.plans)
I'm not really sure what's the best approach for Mongoid because I've never used it. From what I've gather from the documentation, something like the following might work although I'm not running the code.
Plan.where(active: true).not_in(_id: Subscription.where(user_id: current_user.id).pluck(:plan_id))

Many-to-Many Uniqueness Constraint Test Not Working

I have a many-to-many relationship with a join table in my Rails application. I'm using the has_many :through idiom in my models. To keep things simple, lets call my first class Student, my second class Course, and the join table class Enrollment (which contains fields student_id and course_id). I want to make sure that a given Student is associated with a given Course at most once (i.e. the {student_id, course_id} tuple should be unique in the enrollment table).
So I have a migration a that enforces this uniqueness.
def change
add_index :enrollments, [:student_id, :course_id], :unique => true
end
In addition my model classes are defined as such:
class Student < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollment
end
class Course < ActiveRecord::Base
has_many :enrollments
has_many :students, :through => :enrollment
end
class Enrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
validates :student, :presence => true
validates :course, :presence => true
validates :student_id, :uniqueness => {:scope => :course_id}
end
In a rails console, I can do the following:
student = Student.first
course = Course.first
student.courses << course
#... succeeds
student.courses << course
#... appropriately fails and raises an ActiveRecord::RecordInvalid exception
In my RSpec test, I do the exact same thing and I get no exception with the following code:
#student.courses << #course
expect { #student.courses << #course }.to raise_error(ActiveRecord::RecordInvalid)
And so my test fails and reports:
expected ActiveRecord::RecordInvalid but nothing was raised
What's going on here? What could I be doing wrong? How do I fix it?
Rails uses model level validation, if you want strict checking for uniquiness you need to use database level - foreign keys for example. But in this case you need to catch exceptions from database connector.
This is strange because in my code (very similar to your) validation for unique raises exception.
There's a couple of things here that could be happening:
#courses has changed between uses.
#student has changed between uses.
By using let you'll protect these values from changing between expectations.
let(:course) { Course.first }
let(:student) { Student.first }
subject{ student.courses << course << course }
it { should raise_error(ActiveRecord::RecordInvalid) }
Or, there could just be something wrong with your code :)

Resources