PostgreSQL::OID::Array does not support `serialize` feature - ruby-on-rails

I've tried to deploy to Heroku with array column. But this error occurs. And what can I use method instead of serialize is better?
ActiveRecord::AttributeMethods::Serialization::ColumnNotSerializableError (Column `days` of type ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array does not support `serialize` feature.
Usually it means that you are trying to use `serialize`
on a column that already implements serialization natively.
// migration file
class AddDaysToSchedule < ActiveRecord::Migration[5.2]
def change
add_column :event_types, :days, :text, array: true
end
end
// schedule model
class Schedule < ApplicationRecord
serialize :days, Array
Please let me know you knoe how to stack overflow.

Solution 1 :=>
class AddDaysToSchedule < ActiveRecord::Migration[5.2]
def change
add_column :event_types, :days, :string, array: true, default: []
end
end
No need to serialize
class Schedule < ApplicationRecord
#serialize :days, Array
Solution 2:=>
I would suggest you to go like this: -
class AddDaysToSchedule < ActiveRecord::Migration[5.2]
def change
add_column :event_types, :days, :string
end
end
And serialize column in model as array
class Schedule < ApplicationRecord
serialize :days, Array
end
To store values:-
sh = Schedule.new()
sh.days.push(day_value)
sh.save
To get array value
sh = Schedule.find(x)
sh.days => will return array of days

For those trying to use, say Sqlite in development environment, but Postgres in production, you can serialize conditionally based on the database type. Example:
serialize :days, Array if ActiveRecord::Base.connection.adapter_name == 'SQLite'

Related

Rails rename_column changing the column's type

I'm writing a down method in my migration to deal with reverting a migration that replaces a string field with a reference field. Here's the code:
class ChangeCars < ActiveRecord::Migration[5.1]
def up
rename_column :cars, :make_id, :make_id_string
add_reference :cars, :make, foreign_key: true, optional: true
Cars.all.each do |car|
unless car.make_id_string.nil?
make = Make.find_by(uuid: car.uuid)
car.make_id = make
end
car.save!
end
remove_column :cars, :make_id_string
end
def down
add_column :cars, :make_id_string, :string
Cars.all.each do |car|
unless car.make.nil?
car.make_id_string = car.make.uuid
end
car.save!
end
# at this point :make_id_string -> String
remove_reference :cars, :make, index: true, foreign_key: true
rename_column :cars, :make_id_string, :make_id
# at this point :make_id -> Fixnum
end
end
It seems like when I'm removing the reference, I'm not completely flushing it out so when I replace the make_id field, it takes on that fixnum type.
Any suggestions are appreciated!
If you have real data in the make_id_string I would strongly suggest not to remove it. If you have some bugs in the migration, it would save you. Also reverting the migration would be far easier.
If you want to iterate over all models in Car, don't use #each, because if loads all the cars to memory at once. Use #find_each that loads records in batches.

Override attr_readonly in data migration to populate a new read only field on existing records

I have added a UUID field to an existing model. I want it to be read only so it cannot be changed.
Model:
class Token < ActiveRecord::Base
attr_readonly :uuid
before_create :set_uuid, on: create
def set_uuid
self.uuid = SecureRandom.urlsafe_base64(8)
end
end
However I want to populate exiting records with UUIDs. I cannot do this through a default value because they are not generated dynamically.
I could write a custom validator in the model, but this seems like overkill when I only really want to override the attr_readonly in the data migration.
As it stands my data migration does not change the value for existing values from nil.
Data Migration:
class AddUuidToTokens < ActiveRecord::Migration
def self.up
Token.all.each do |token|
if token.uuid.nil?
token.uuid = SecureRandom.urlsafe_base64(8)
token.save!
end
end
end
You could just override the Token class itself in the migration:
class AddUuidToTokens < ActiveRecord::Migration
class Token < ActiveRecord::Base
end
def self.up
Token.where(uuid: nil).find_each do |token|
token.update_columns(uuid: SecureRandom.urlsafe_base64(8))
end
end
end
Minor improvement: Load only records without an uuid instead of checking all records against nil?.

Set new column value from serialized hash in migration

I have a model which has some information which is best stored as a serialized Hash on the model, as it is unimportant to most of the app and varies from instance to instance:
class Foo < AR::Base
attr_accessible :name, :fields
serialize :fields
end
I have realised that one of the common entries in fields actually is relevant to the app, and would be better placed as an attribute (layout).
Bearing in mind that I should not, ideally, refer to models in migrations, how can I write a migration to add the layout field, and initialise it with the value currently in the fields Hash?
class AddLayoutToCardTemplates < ActiveRecord::Migration
def change
add_column :card_templates, :layout, :string, default: 'normal'
# Initialise `layout` from `fields['layout']`... how? With raw SQL?
end
end
You should not refer to models in your app folder. This doesn't mean you cannot create local model. :)
class AddLayoutToCardTemplates < ActiveRecord::Migration
class Foo < AR::Base
attr_accessible :name, :fields
serialize :fields
end
def change
add_column :card_templates, :layout, :string, default: 'normal'
Foo.all.each do |f|
f.layout = f.fields.delete(:layout)
f.save
end
end
That way your migration can use ActiveRecord goodies and yet stays time-independent, as your real model in app folder is never loaded.

Rails: Adding migration to add an array (default empty)

I'm trying to add a column called share to one of my resources.
The idea is that users can upload documents and share them with other (specific) users, and the array contains the emails of those that the user wants to share with.
I tried adding a migration with the code
class AddShareToDocuments < ActiveRecord::Migration
def change
add_column :documents, :share, :array, :default => []
end
end
But when I open up rails console in the command prompt, it says that share:nil and user.document.share.class is NilClass.
Creating a new array in the rails console sandbox by typing
newarray = []
says that newarray.class is Array.
Can anyone spot what I'm doing wrong?
Rails 4 the PostgreSQL Array data type
In terminal
$ rails generate migration AddTagsToProduct tags:string
Migration file:
class AddTagsToProduct < ActiveRecord::Migration
def change
add_column :products, :tags, :string, array: true, default: []
end
end
https://coderwall.com/p/sud9ja/rails-4-the-postgresql-array-data-type
if you want support all databases you must serialize the array in a String
class Documents < ActiveRecord::Base
serialize :share
end
class AddShareToDocuments < ActiveRecord::Migration
def change
add_column :documents, :share, :string, :default => []
end
end
In case of Postgresql and array datatype I found https://coderwall.com/p/sud9ja
Arrays are not normally a type to be stored in a database. As michelemina points out, you can serialize them into a string and store them, if the type of the data in the array is simple (strings, int, etc). For your case of emails, you could do this.
If, on the other hand, you want to be able to find all of the User objects that a document was shared with, there are better ways of doing this. You will want a "join table". In your case, the join-table object may be called a Share, and have the following attributes:
class Share
belongs_to :user
belongs_to :document
end
Then, in your Document class,
has_many :shares
has_many :users, :through => :shares
As far as generating the migration, this may be hacky, but you could create a new migration that changes the type to "string" (Edit: correct code):
class AddShareToDocuments < ActiveRecord::Migration
def up
change_column :documents, :share, :string
end
def down
change_column :documents, :share, :array, :default => []
end
end

Default values for model attributes in Rails

What's the proper way to set default values for models in Rails?
class User < ActiveRecord::Base
attr_accessible :name, :points
end
I want points to start out at 0 instead of nil. Ideally the default value is created right away rather than waiting for the User to be saved into the database. But I guess using a before_save or database constraints work as well:
class User < ActiveRecord::Base
attr_accessible :name, :points
before_save :set_defaults
private
def set_defaults
self.points = 0
end
end
Using the latest stable Rails.
set it in your migration:
t.integer :points, default: 0
You can set default value by migration or by using model setter methods.
change_column :user, :points, :integer, :default => 0
Or,
def points
#points ||= 0
end

Resources