Rails: Adding migration to add an array (default empty) - ruby-on-rails

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

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.

PostgreSQL::OID::Array does not support `serialize` feature

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'

How to store an array of numbers in database using rails

I am pretty new to rails so this may be a dumb question, but I have spent hours debugging this seemingly simple error and gotten nowhere. In my database, I want each row to to store an array of numbers (in a variable called contributions). I read that the best way to do that was by adding the following code:
class Goal < ActiveRecord::Base
#...
serialize :contributions, Array
#...
end
The attribute is also in goal.rb
ActiveRecord::Schema.define(version: 20150711171452) do
create_table "goals", force: :cascade do |t|
#...
t.decimal "contributions"
#...
end
end
I added it via this migration:
class AddContributorsToGoals < ActiveRecord::Migration
def change
add_column :goals, :contributors, :string
add_column :goals, :contributions, :decimal
end
end
The only problem is that whenever I try to access this attribute, I always get errors stating that it is of type BigDecimal. Rails thinks that this is a single number, but I want it to be a list of numbers. Any ideas as to what I could be doing wrong?
One example of a problem I get is that when this executes:
#goal.contributions << #goal.lastUpdateAmount
but I get this error
undefined method `<<' for #<BigDecimal:7f95e082ab18,'-0.0',9(9)>
I followed identical steps with an array of strings and that was serialized properly. I just don't know what's going on with this one.
Please change data type decimal to text
class AddContributorsToGoals < ActiveRecord::Migration
def change
add_column :goals, :contributions, :text
end
end

Spree Relation Issue

I'm trying to add product attachment functionality to a Spree store. E.g. a product has many attached documents: brochures, instruction manuals, etc. I can't get the relationship between documents and products to work.
I can use the Paperclip gem for the attachment functionality since Spree already uses it for images.
I have the "document" model: models/spree/document.rb:
class Spree::Document < ActiveRecord::Base
belongs_to :products, class_name: "Spree::Product"
has_attached_file :pdf
end
Then I try to relate the document model to the Spree::Product model in models/spree/product_decorator.rb:
Spree::Product.class_eval do
has_many :documents, dependent: :destroy
end
Then I add migrations:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :spree_documents do |t|
t.timestamps
end
end
end
class AddPdfToDocuments < ActiveRecord::Migration
def self.up
add_attachment :spree_documents, :pdf
end
def self.down
remove_attachment :spree_documents, :pdf
end
end
Now I go into the rails console to see if it worked:
#=> prod = Spree::Product.first
#=> prod.document
#=> PG::UndefinedColumn: ERROR: column spree_documents.product_id does not exist
#=> LINE 1: ..."spree_documents".* FROM "spree_documents" WHERE "spree_doc...
^
#=> : SELECT "spree_documents".* FROM "spree_documents" WHERE "spree_documents"."product_id" = $1
Seems like I'm not defining the relationship between documents and products correctly, but I'm not sure what the issue is.
It looks like you never added a product_id column to your Spree::Documents table. When you define a model belongs_to another model, it tells ActiveRecord that the first one will be a [relation]_id column in its table.
You just need to make sure to add t.references :product in your migration, so it'd look like:
class CreateDocuments < ActiveRecord::Migration
def change
create_table :spree_documents do |t|
t.references :product
t.timestamps
end
end
end

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.

Resources