Rails 5 , sqlite3 using array as column/ attribute issue - ruby-on-rails

Here is how I use array in a rails 5 model
in migration
t.text :diagnoses, array: true, default: []
in model
class Patient < ApplicationRecord
serialize :diagnoses, Array
end
in my seed method I am doing it like
patient = Patient.create(first_name: 'John', last_name: 'Smith', admission: a)
patient.diagnoses = [1, 2]
patient.save!
It give an error as
ActiveRecord::SerializationTypeMismatch: can't dump `diagnoses`: was supposed to be a Array, but was a Integer. -- 0
Thanks for any help!

A while ago, I encountered this exact issue. I found the following workaround:
In your migration file:
t.text :diagnoses, array: true
Then in model:
class Patient < ApplicationRecord
serialize :diagnoses
after_initialize do |patient|
patient.diagnoses= [] if patient.diagnoses == nil
end
end
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database.

I would seriously consider actually using a relational database properly instead.
# since diagnosis is highly irregular we most likely need to configure rails
# to pluralize it correctly
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.irregular 'diagnosis', 'diagnoses'
end
class Patient < ApplicationRecord
has_many :patient_diagnoses
has_many :diagnoses, through: patient_diagnoses
end
# this table provides data normalization
class Diagnosis < ApplicationRecord
has_many :patient_diagnoses
has_many :patients, through: patient_diagnoses
end
# this is just a join table
class PatientDiagnosis < ApplicationRecord
belongs_to :patient
belongs_to :diagnosis
end
This lets you use the foreign keys to ensure referential integrity and lets you use ActiveRecord Associations instead of just cobbling together something wonky. There are very few actual advantages of using an array type here.
If you still want to use your array column you should not use ActiveRecord::AttributeMethods::Serialization. Its used with plain old varchar / text columns to store YAML strings which are serialized/unserialized in Rails. It is a vestige from the dark days before we had native JSON/array types and really does not have any use today besides in legacy applications.

Related

many-to-many relationship in rails

I have the following models
class Order < ApplicationRecord
has_many :order_details
has_many :products, through: :order_details
end
class OrderDetail < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :order_details
has_many :orders, through: :order_details
end
And I already have product records in my database.
Now, if using syntax: Order.create name: 'HH', product_ids: [1,2]
1 Order record is created, and rails automatically creates 2 more OrderDetail records to connect that Order record with 2 Products.
This syntax is quite handy.
Now, I want to learn more about it from the Rails documentation. But now i still can't find the documentation about it. Can someone help me find documents to learn more?
[Edit] Additional: I'd like to find documentation on the Rails syntax that allows passing a list of ids to automatically create records in the intermediate table, like the Order.create syntax with ```product_ids` `` that I gave above.
The extensive documentation is at https://api.rubyonrails.org/, and many-to-many is here.
The essential part is to analyze the source code of Rails at Module (ActiveRecord::Associations::CollectionAssociation) and at id_writers method:
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
primary_key = reflection.association_primary_key
pk_type = klass.type_for_attribute(primary_key)
ids = Array(ids).compact_blank
ids.map! { |i| pk_type.cast(i) }
# .... code continues
We see that ids parameter (ex.: [1,2]) is first checked to be Array then the compact_blank method removes all falses values, after that, ids are casted to match primary_key type of the model (usually :id). Then code continues to query database with where to get found ids (associations) and saves.

How can I migrate a has_many / belongs_to relationship in rails to has_and_belongs_to_many?

I have an existing rails 6 application where I have two models:
class Reservation << ApplicationRecord
# ...
has_many :charges
# ...
end
class Charge << ApplicationRecord
# ...
belongs_to :reservation
# ...
end
I want to refactor it to this:
class Reservation << ApplicationRecord
# ...
has_and_belongs_to_many :charges
# ...
end
class Charge << ApplicationRecord
# ...
has_and_belongs_to_many :reservation
# ...
end
What I want to know is how to write that migration? There's already data in the table, so I need to retain existing charges whose reservation IDs are set and keep the link.
Be careful here, and make sure you can revert if there's a mistake, so you don't lose your data!
First you need to create the join table with a migration. You can create the migration from the command-line with:
rails g migration create_charges_reservations
this should create the template of the migration for you in db/migrate, which you'll populate according to your need like this:
class CreateChargesReservations < ActiveRecord::Migration[6.0]
def change
create_table charges_reservations do |t|
t.integer :charge_id
t.integer :reservation_id
end
end
end
run the migration from the command line:
rails db:migrate
Now make a join model:
# app/models/charges_reservation.rb
class ChargesReservation < ApplicationRecord
belongs_to :charge
belongs_to :reservation
end
Now you have to migrate the existing data, so from the rails console:
Charge.all.each{|c| ChargesReservation.create(charge_id: c.id, reservation_id:c.reservation_id)}
And finally change the associations to habtm associations as you have indicated in your question
# charge.rb
has_and_belongs_to_many :reservations
#reservation.rb
has_and_belongs_to_many :charges
Oh and you can delete the reservation_id column in the charges table with another migration, once you are sure everything is working correctly. This is the point where you could create a problem b/c you're destroying data, so be sure that the join table was correctly populated.
You actually don't need the join model any longer either, it was just a convenient way to populate the join table. So you can delete the charges_reservation.rb model.

Specify an optional reference in your Rails model

I have a Sponsors model and a Promo Codes model.
A sponsor can have zero or more promo codes
A promo code can have zero or one sponsors
Thus a promo code should have an optional reference to a sponsor, that is, a sponsor_id that may or may not have a value. I'm not sure how to set this up in Rails.
Here's what I have so far:
# app/models/sponsor.rb
class Sponsor < ActiveRecord::Base
has_many :promo_codes # Zero or more.
end
# app/models/promo_code.rb
class PromoCode < ActiveRecord::Base
has_one :sponsor # Zero or one.
end
# db/migrate/xxxxx_add_sponsor_reference_to_promo_codes.rb
# rails g migration AddSponsorReferenceToPromoCodes sponsor:references
# Running migration adds a sponsor_id field to promo_codes table.
class AddSponsorReferenceToPromoCodes < ActiveRecord::Migration
def change
add_reference :promo_codes, :sponsor, index: true
end
end
Does this make sense? I'm under the impression that I have to use belongs_to in my Promo Codes model, but I have no basis for this, just that I've haven't seen a has_many with has_one example yet.
In Rails 5, belongs_to is defined as required by default. To make it optional use the 'optional' option :)
class User
belongs_to :company, optional: true
end
Source: https://github.com/rails/rails/issues/18233
This looks like a simple has_many and belongs_to relationship:
# app/models/sponsor.rb
class Sponsor < ActiveRecord::Base
has_many :promo_codes # Zero or more.
end
# app/models/promo_code.rb
#table has sponsor_id field
class PromoCode < ActiveRecord::Base
belongs_to :sponsor # Zero or one.
end
has_one isn't appropriate here, as it would replace has_many: ie, you either have "has_many" and "belongs_to" OR "has_one" and "belongs_to". has_one isn't generally used much: usually it is used when you already have a has_many relationship that you want to change to has_one, and don't want to restructure the existing tables.
Unless you specify validation, relationships are optional by default.
The belongs_to is to tell rails the other half of the relationship between those two objects so you can also call #promo_code.sponsor and, vice versa, #sponsor.promo_codes.

Why polymorphic association doesn't work for STI if type column of the polymorphic association doesn't point to the base model of STI?

I have a case of polymorphic association and STI here.
# app/models/car.rb
class Car < ActiveRecord::Base
belongs_to :borrowable, :polymorphic => true
end
# app/models/staff.rb
class Staff < ActiveRecord::Base
has_one :car, :as => :borrowable, :dependent => :destroy
end
# app/models/guard.rb
class Guard < Staff
end
In order for the polymorphic assocation to work, according to the API documentation on Polymorphic Assocation, http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations that I have to set borrowable_type to the base_classof STI models, that is in my case is Staff.
The question is: Why doesn't it work if the borrowable_type set to STI class?
Some test to prove it:
# now the test speaks only truth
# test/fixtures/cars.yml
one:
name: Enzo
borrowable: staff (Staff)
two:
name: Mustang
borrowable: guard (Guard)
# test/fixtures/staffs.yml
staff:
name: Jullia Gillard
guard:
name: Joni Bravo
type: Guard
# test/units/car_test.rb
require 'test_helper'
class CarTest < ActiveSupport::TestCase
setup do
#staff = staffs(:staff)
#guard = staffs(:guard)
end
test "should be destroyed if an associated staff is destroyed" do
assert_difference('Car.count', -1) do
#staff.destroy
end
end
test "should be destroyed if an associated guard is destroyed" do
assert_difference('Car.count', -1) do
#guard.destroy
end
end
end
But it seems to be true only with Staff instance. The results are:
# Running tests:
F.
Finished tests in 0.146657s, 13.6373 tests/s, 13.6373 assertions/s.
1) Failure:
test_should_be_destroyed_if_an_associated_guard_is_destroyed(CarTest) [/private/tmp/guineapig/test/unit/car_test.rb:16]:
"Car.count" didn't change by -1.
<1> expected but was
<2>.
Thanks
Good question. I had exactly the same problem using Rails 3.1. Looks like you can not do this, because it does not work. Probably it is an intended behavior. Apparently, using polymorphic associations in combination with Single Table Inheritance (STI) in Rails is a bit complicated.
The current Rails documentation for Rails 3.2 gives this advice for combining polymorphic associations and STI:
Using polymorphic associations in combination with single table
inheritance (STI) is a little tricky. In order for the associations to
work as expected, ensure that you store the base model for the STI
models in the type column of the polymorphic association.
In your case the base model would be "Staff", i.e. "borrowable_type" should be "Staff" for all items, not "Guard". It is possible to make the derived class appear as the base class by using "becomes" : guard.becomes(Staff). One could set the column "borrowable_type" directly to the base class "Staff", or as the Rails Documentation suggests, convert it automatically using
class Car < ActiveRecord::Base
..
def borrowable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
An older question, but the issue in Rails 4 still remains. Another option is to dynamically create/overwrite the _type method with a concern. This would be useful if your app uses multiple polymorphic associations with STI and you want to keep the logic in one place.
This concern will grab all polymorphic associations and ensure that the record is always saved using the base class.
# models/concerns/single_table_polymorphic.rb
module SingleTablePolymorphic
extend ActiveSupport::Concern
included do
self.reflect_on_all_associations.select{|a| a.options[:polymorphic]}.map(&:name).each do |name|
define_method "#{name.to_s}_type=" do |class_name|
super(class_name.constantize.base_class.name)
end
end
end
end
Then just include it in your model:
class Car < ActiveRecord::Base
belongs_to :borrowable, :polymorphic => true
include SingleTablePolymorphic
end
Just had this issue in Rails 4.2. I found two ways to resolve:
--
The problem is that Rails uses the base_class name of the STI relationship.
The reason for this has been documented in the other answers, but the gist is that the core team seem to feel that you should be able to reference the table rather than the class for a polymorphic STI association.
I disagree with this idea, but am not part of the Rails Core team, so don't have much input into resolving it.
There are two ways to fix it:
--
1) Insert at model-level:
class Association < ActiveRecord::Base
belongs_to :associatiable, polymorphic: true
belongs_to :associated, polymorphic: true
before_validation :set_type
def set_type
self.associated_type = associated.class.name
end
end
This will change the {x}_type record before the creation of the data into the db. This works very well, and still retains the polymorphic nature of the association.
2) Override Core ActiveRecord methods
#app/config/initializers/sti_base.rb
require "active_record"
require "active_record_extension"
ActiveRecord::Base.store_base_sti_class = false
#lib/active_record_extension.rb
module ActiveRecordExtension #-> http://stackoverflow.com/questions/2328984/rails-extending-activerecordbase
extend ActiveSupport::Concern
included do
class_attribute :store_base_sti_class
self.store_base_sti_class = true
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
####
module AddPolymorphic
extend ActiveSupport::Concern
included do #-> http://stackoverflow.com/questions/28214874/overriding-methods-in-an-activesupportconcern-module-which-are-defined-by-a-cl
define_method :replace_keys do |record=nil|
super(record)
owner[reflection.foreign_type] = ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name
end
end
end
ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, AddPolymorphic)
A more systemic way to fix the issue is to edit the ActiveRecord core methods which govern it. I used references in this gem to find out which elements needed to be fixed / overridden.
This is untested and still needs extensions for some of the other parts of the ActiveRecord core methods, but seems to work for my local system.
There is a gem.
https://github.com/appfolio/store_base_sti_class
Tested and it works on various versions of AR.
You can also build a custom scope for a has_* association for the polymorphic type:
class Staff < ActiveRecord::Base
has_one :car,
->(s) { where(cars: { borrowable_type: s.class }, # defaults to base_class
foreign_key: :borrowable_id,
:dependent => :destroy
end
Since polymorphic joins use a composite foreign key (*_id and *_type) you need to specify the type clause with the correct value. The _id though should work with just the foreign_key declaration specifying the name of the polymorphic association.
Because of the nature of polymorphism it can be frustrating to know what models are borrowables, since it could conceivably be any model in your Rails application. This relationship will need to be declared in any model where you want the cascade deletion on borrowable to be enforced.
This is how I solved that problem using aforementioned hints:
# app/models/concerns/belongs_to_single_table_polymorphic.rb
module BelongsToSingleTablePolymorphic
extend ActiveSupport::Concern
included do
def self.belongs_to_sti_polymorphic(model)
class_eval "belongs_to :#{model}, polymorphic: true"
class_eval 'before_validation :set_sti_object_type'
define_method('set_sti_object_type') do
sti_type = send(model).class.name
send("#{model}_type=", sti_type)
end
end
end
end
and with that, for any model in which I would find belongs_to :whatever, polymorphic: true I do:
class Reservation < ActiveRecord::Base
include BelongsToSingleTablePolymorphic
# .....
belongs_to_sti_polymorphic :whatever
# .....
end
I agree with the general comments that this ought to be easier. That said, here is what worked for me.
I have a model with Firm as the base class and Customer and Prospect as the STI classes, as so:
class Firm
end
class Customer < Firm
end
class Prospect < Firm
end
I also have a polymorphic class, Opportunity, which looks like this:
class Opportunity
belongs_to :opportunistic, polymorphic: true
end
I want to refer to opportunities as either
customer.opportunities
or
prospect.opportunities
To do that I changed the models as follows.
class Firm
has_many opportunities, as: :opportunistic
end
class Opportunity
belongs_to :customer, class_name: 'Firm', foreign_key: :opportunistic_id
belongs_to :prospect, class_name: 'Firm', foreign_key: :opportunistic_id
end
I save opportunities with an opportunistic_type of 'Firm' (the base class) and the respective customer or prospect id as the opportunistic_id.
Now I can get customer.opportunities and prospect.opportunities exactly as I want.

How can I use Mongoid and ActiveRecord in parallel in Rails 3?

I'm using rails 3, and began my application with ActiveRecord. Now, I have many models, and the relations are starting to get complicated, and some could be more simply expressed with a Document-Oriented structure, so I'd like to try migrating to MongoDB and use Mongoid.
I've always heard that you didn't have to eitheer use all MongoDB or nothing, but that you could use the two in parallel while migrating. I don't see how to go about this from the docs though.
For example, I have:
class User < ActiveRecord::Base
has_many :items
has_many :products, :through => :items
end
class Product < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :user
belongs_to :product
# alot of data that fits a hierarchical document-oriented structure
end
I'd like to ideally begin by replacing my Item activerecord model with a Mongoid document, so my items are stored in MongoDB, and my Users and Products can stay in my SQL DB
Thing is, I don't see how to do this. Am I going about this the right way?
Perhaps another alternative is to keep a base AR Item
class Item < ActiveRecord::Base
has_one :mongodb_item ?? # I know this is wrong
end
class MongodbItem
include Mongoid::Document
belongs_to AR_Item ??? # I know this is also wrong
end
Thanks!
What I did was just mock the relationship with methods in each the AR model and the Mongoid model like so.
# visit_session.rb
class VisitSession
include Mongoid::Document
include Mongoid::Timestamps
field :user_id, type: Integer
index({user_id: 1},{name: :user_id_index})
# Mock a belongs_to relationship with User model
def user
User.find(self.user_id)
end
end
# user.rb
class User < ActiveRecord::Base
# Mock a has_many relationship with VisitSession Mongoid model
def visit_sessions
VisitSession.where(user_id: self.id)
end
end
Of course you won't have all the AR methods on VisitSession Mongoid model but you'll at least be able to mock the relationship between the two fairly well.
Hope this helps.
I don't see any reason why you couldn't have both ActiveRecord and Mongoid models in the same application. That being said, I'm almost certain that you'll run into issues if you try to create relationships between your ActiveRecord and Mongoid models.
If your ActiveRecord models are heavily inter-related, but better suited to a document structure, then I would suggest just biting the bullet and converting them all to Mongoid documents. I had to do this recently on a (large-ish) project, and it's significantly less stressful than you would think.
If you have good unit tests for your models, then it should be a total snap. If you don't - write your unit tests first, make sure they pass with ActiveRecord and then start migrating things over to Mongoid.
i created a module for spoofing the relation in active record models.
module MongoRelations
def belongs_to_mongo(name, options = {})
id_name = "mongo_#{name}_id".to_sym
mongo_model = options[:through] || "Mongo::#{name.to_s.camelize}".constantize
define_method(name) do
id = send(id_name)
mongo_model.find(id) if id.present?
end
define_method("#{name}=") do |value|
send("#{id_name}=".to_sym, value.try(:id).to_s)
end
end
end
In my SQL table, I name my mongo relations using the convention mongo_XXX_id, instead of XXX_id
I also namespace all my mongo models under Mongo::
in my active record model
class Foo < ActiveRecord::Base
belongs_to_mongo :XXX
end
which allows
Foo.new.XXX = Mongo.find('123')
Foo.XXX
or
Foo.new.XXX_id = '123'
Foo.XXX
... just for tracking purpose,
I'd like to add something I just found out on this field:
DRY up your SQL+NoSQL Rails projects

Resources