Rails enums preventing destroy actions in PG - ruby-on-rails

I have a whole bunch of enums in my application where I use a hash to define the enums like so:
class Event < ApplicationRecord
enum eventable_type: { comment: 0, review: 1, track: 2, follow: 3, unfollow: 4,
favorite: 5, unfavorite: 6, purchase: 7, blog: 8,
listing: 9, album: 10, pseudonym: 11, topic: 12, notation: 13,
wiki: 14, like: 15, dislike: 16}
belongs_to :user
belongs_to :eventable, polymorphic: true
end
Now in my other models, for example the Track model, I have something like this:
class Track < ApplicationRecord
belongs_to :user, counter_cache: true
has_many :comments, as: :commentable
has_many :events, as: :eventable, dependent: :destroy
The enums work great until I need to destroy a Track. If I try to do it in the rails console, the problem arises when the PG database tried to destroy the events associated with the Track first.
Event Destroy (1.8ms) DELETE FROM "events" WHERE "events"."eventable_id" = $1 AND "events"."eventable_type" = $2 [["eventable_id", 10], ["eventable_type", "Track"]]
TRANSACTION (1.1ms) ROLLBACK
Traceback (most recent call last):
1: from (irb):3
ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR: invalid input syntax for integer: "Track")
I was hoping it would just work, as many of the rails tutorials say to set up enums this way.
Rails version:
Rails 6.1.0.alpha
Ruby version:
ruby 2.6.0p0
How do I get PG to use the integer value instead of the string part of the enum? Is there a way to make it work without migrating all my tables to have a new PG enum setup? I could do that, but then I would have to figure out how to change every record in the database to reflect the new values.

I managed to solve this issue by foregoing the enum values for the _types (commentable_type, eventable_type, etc.) by changing them from integers to strings. First, I made a migration to add a new column to the database while renaming the old columns so I could easily migrate the data afterwards.
class ChangeFromEnumIntegersToStrings < ActiveRecord::Migration[6.1]
def change
rename_column :events, :eventable_type, :eventable_type_old
rename_column :flags, :flaggable_type, :flaggable_type_old
rename_column :flags, :flag_type, :flag_type_old
rename_column :notifications, :commentable_type, :commentable_type_old
rename_column :notifications, :notifiable_type, :notifiable_type_old
add_column :events, :eventable_type, :string
add_column :flags, :flaggable_type, :string
add_column :flags, :flag_type, :string
add_column :notifications, :commentable_type, :string
add_column :notifications, :notifiable_type, :string
end
end
Once I migrated the database, I then went into the rails console and just changed all the records like so for each of the old enum values.
Notification.where(:notifiable_type_old => 0).update_all("notifiable_type = 'comment'")

Related

Problems with money-rails in Ruby

I was following a tutorial to use money-rails in a new project.
Here is my migration file:
class AddFieldsToPlan < ActiveRecord::Migration[5.1]
def change
add_column :plans, :payment_gateway_plan_identifier, :string
add_column :plans, :price, :integer
add_column :plans, :interval, :integer
add_column :plans, :interval_count,:integer
add_column :plans, :status,:integer
remove_column :plans, :amount
remove_column :plans, :payment_frequency
end
end
And my model:
class Plan < ApplicationRecord
enum status: {inactive: 0, active: 1}
enum interval: {day: 0, week: 1, month: 2, year: 3}
monetize :price_cents
def end_date_from(date = nil)
date ||= Date.current.to_date
interval_count.send(interval).from_now(date)
end
end
I read all the API specification of money-rails but doesnt understand well I guess.
If I run the rails console, and do a Plan.last.price it shows me this error:
.3.4 :001 > Plan.last.price
Plan Load (2.6ms) SELECT "plans".* FROM "plans" ORDER BY "plans"."id" DESC LIMIT $1 [["LIMIT", 1]]
NoMethodError: undefined method `price_cents' for #<Plan:0x007f8ca807f8f0>
Did you mean? price_cents=
from (irb):1
What Im doing wrong here? How can I set up a value for this price attribute?
Thanks
Look at the tutorial for `money-rails' you'll see the migration they recommend is
add_monetize :products, :price # Rails 4x and above
That actually creates an integer field called price_cents in the model.
You need another migration to remove price and then use the above line to add the price_cents to the table.

Join table has two plural words. How to properly name models and migrations; in order to destroy orphans on join-table. Active Record

Okay, setting aside the fact that asking how to "destroy orphans" on a chat board is just evil, here is my technical question:
I am making a simple blog-site, using Activerecord and Sinatra (and I am new to both). I have a "many to many" relationship between a Post model and a Tag model, and have been getting errors that I was not able to sort out. Finally, I arranged the models and migration so that I am not getting errors; however, when I destroy an instance of a tag, the the associations on the "posts_tags" table are left in place (existing as orphans), and I want to know how to destroy them (cue evil music).
Can anyone assist me with this?
Here are my migration for all three tables:
class CreatePostsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title, default: 'title here'
t.string :content, default: 'content here'
t.belongs_to :blog, index: true
t.timestamps
end
end
end
class CreateTagTable < ActiveRecord::Migration[5.2]
def change
create_table :tags do |t|
t.string :name, unique: true
end
end
end
class CreatePostsTagsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts_tags, :id => false do |t|
t.belongs_to :tag, index: true
t.belongs_to :post, index: true
# t.integer :post_id
# t.integer :tag_id
end
end
end
And here are my three models:
file-name: Post_Tag (I have named, and re-named this class and file-name over the course of a couple days).
class PostsTag < ActiveRecord::Base
# self.table_name = "posts_tags" #had to add this line for seed file not to give errors
belongs_to :post
belongs_to :tag
end
class Post < ActiveRecord::Base
belongs_to :blog
has_many :posts_tags
has_many :tags, :through => :posts_tags
has_many :comments, dependent: :destroy
end
class Tag < ActiveRecord::Base
has_many :posts_tags
has_many :posts, :through => :posts_tags
# has_and_belongs_to_many :posts, :through => :posts_tags
end
This configuration is not giving me error when I search for a post's tags, or a tag's posts, BUT when I do #tag = Tag.find_by_id(1) #tag.destroy the "posts_tags" table is left still having all of the tag_id == 1 rows still there. When I try to destroy them, I get errors (which I think is because that would also destroy the associations).
Does anyone know how to fix this? Is it okay to simply remove the PostsTag model all together?
The Activerecord documentation uses an example where the join-table is a single word, so I wasn't able to find the answer there.
I saw that some people were simply not making a model for the join-table. When I removed my join-table model, I got this error:
#post = Post.find_by_id(1)
#post.tags.each do |tag|
p tag.name
end
NameError at /
uninitialized constant Tag::PostsTag
Thanks for any help!
#
Phase Two of question:
After the tip that I could try adding this code to my models:
class Post < ActiveRecord::Base
has_many :posts_tags, dependent: :destroy
class Tag < ActiveRecord::Base
I am running this in my server/app file:
delete "/tag/destroy" do
body = JSON.parse request.body.read # body: {id: number} or {name: string}
#tag_to_destroy = Tag.where(body)[0]
#tag_to_destroy.destroy
redirect "/tag"
end
I am sending this request via postman:
http://localhost:4567/tag/destroy
body of request:
{
"id": 12
}
And I am getting this error:
(even though there is are rows in the posts_tags database with tag_id = 12, post_id = variousNumbers):
== Sinatra (v2.0.1) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 0, max threads: 16
* Environment: development* Listening on tcp://localhost:4567
Use Ctrl-C to stop
D, [2018-05-07T10:54:19.604906 #99099] DEBUG -- : Tag Load (0.5ms) SELECT "tags".* FROM "tags" WHERE "t
ags"."id" = $1 [["id", 12]]
D, [2018-05-07T10:54:19.617955 #99099] DEBUG -- : (0.3ms) BEGIN
D, [2018-05-07T10:54:19.633736 #99099] DEBUG -- : PostsTag Load (1.5ms) SELECT "posts_tags".* FROM "pos
ts_tags" WHERE "posts_tags"."tag_id" = $1 [["tag_id", 12]]
D, [2018-05-07T10:54:19.642305 #99099] DEBUG -- : PostsTag Destroy (1.6ms) DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
D, [2018-05-07T10:54:19.642685 #99099] DEBUG -- : (0.2ms) ROLLBACK
2018-05-07 10:54:19 - ActiveRecord::StatementInvalid - PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
^
: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL:
/Users/maiya/.rvm/gems/ruby-2.5.1/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:603:in `async_exec'
Question update:
The join-table migration was previously:
create_table :posts_tags :id => false do |t|
t.belongs_to :tag, index: true
# comment: the above line creates: t.integer :post_id
t.belongs_to :post, index: true
# comment: the above line creates: t.integer :tag_id
end
All is OK now with your migrations and models, you just need to change in the Post and Tag models:
has_many :posts_tags
to
has_many :posts_tags, dependent: :destroy
In this case after you destroy any tag or post all associated posts_tags are destroyed too, it will ensure referential integrity of your app and prevent errors.
Also, it is good idea to rollback migrations, add foreign_key: true option to t.belongs_to lines, and migrate it again. This option will provide referential integrity on db level.
UPDATE:
has_and_belongs_to_many (HABTM) doesn't work with through option, you mixes it up with has_many association. Please, read about it more in guides
To make it all working you need to:
remove :id => false option from CreatePostsTagsTable migration
remigrate or drop and recreate db
change has_and_belongs_to_many <...>, :through => :posts_tags to has_many <...>, :through => :posts_tags in both Tag and Post models
P.S. By convention all file names in ruby are in lower snake case, it should be `posts_tag.rb', not 'Posts_Tag.rb'

Rails: remove reference relationship

I was confused with following code in tutorial.
The goal is to remove reference key genre_id from table books
class RemoveGenreFromBooks < ActiveRecord::Migration
def up
remove_index :books, column: [:genre_id]
remove_column :books, :genre_id
end
def down
add_reference :books, :genre, index: true
end
end
But I don't understand what remove_index :books, column: [:genre_id] mean
Furthermore, I didn't get that index: true in down method.
If I need to add a relationship, why I can not just type
class Addrelationship < ActiveRecord::Migration
def change
add_reference :books, :genre
end
As there is a method to add a referecen, there is a one to remove also - remove_reference
Syntax is: remove_reference(table_name, ref_name, options = {})
So in your case, to remove the reference of Genre :
class RemoveGenreFromBooks < ActiveRecord::Migration
def change
remove_reference :books, :genre, index:true, foreign_key: true
end
end
The option foreign_key: true will also remove the foreign key from the books table.
rake db:rollback STEP=1
Is a way to do this, if the migration you want to rollback is the last one applied. You can substitute 1 for however many migrations you want to go back.

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

Rails 2.3.8 Association Problem has_many belongs_to

I'm new to Rails. I have two models, Person and Day.
class Person < ActiveRecord::Base
has_many :days
end
class Day < ActiveRecord::Base
belongs_to :person
has_many :runs
end
When I try to access #person.days I get an SQL error:
$ script/consoleLoading development environment (Rails 2.3.8)
ree-1.8.7-2010.02 > #person = Person.first
=> #<Person id: 1, first_name: "John", last_name: "Smith", created_at: "2010-08-29 14:05:50", updated_at: "2010-08-29 14:05:50"> ree-1.8.7-2010.02
> #person.days
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: days.person_id: SELECT * FROM "days" WHERE ("days".person_id = 1)
I setup the association between the two before running any migrations, so I don't see why this has not been setup correctly.
Any suggestions?
Telling your model about the association doesn't set up the foreign key in the database - you need to create an explicit migration to add a foreign key to whichever table is appropriate.
For this I'd suggest:
script/generate migration add_person_id_to_days person_id:integer
then take a look at the migration file it creates for you to check it's ok, it should be something like this:
class AddPersonIdToDays < ActiveRecord::Migration
def self.up
add_column :days, :person_id, :integer
end
def self.down
remove_column :days, :person_id
end
end
Run that and try the association again?

Resources