Problems with money-rails in Ruby - ruby-on-rails

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.

Related

Rails enums preventing destroy actions in PG

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'")

Rails string as a foreign key

I have a relation between User and Course (typical enrollment data). A User has_many Course and vice-versa (typical JOIN table scenario).
I am attempting to migrate my previous has_and_belongs_to_many relationship between these two models to a has_many :through relationship. My files currently look like:
class User < ActiveRecord::Base
has_and_belongs_to_many :courses
end
and
class Course < ActiveRecord::Base
has_and_belongs_to_many :users
end
and the table name that joins the two models is courses_users.
I now need to migrate this relationship to the has_many :through association, and also make the column type of user_id a string, as I want to use the g_number (string) attribute of User as the foreign key. Note: I don't care about the performance difference between int and varchar/string.
The short and simple problem is that I need users.g_number to reference enrollments.user_id as a foreign key, and both are strings.
My attempt at a migration and model rework is this:
class User < ActiveRecord::Base
has_many :enrollment
has_many :courses, :through => :enrollment
end
and
class Course < ActiveRecord::Base
has_many :enrollment
has_many :users, :through => :enrollment
end
lastly
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
then the migration
class ChangeUserIdJoin < ActiveRecord::Migration
def self.up
rename_table :courses_users, :enrollments
end
def self.down
rename_table :enrollments, :courses_users
end
end
Everything works fine here. I can do queries like User.courses and Course.users. But now I want to change the type of the user_id column in the join table to a string so that I can store the g_number (string attribute on User) and join on that instead of the serial id column of User.
When I attempt to change the user_id column type to string in the migration:
class ChangeUserIdJoin < ActiveRecord::Migration
def self.up
change_column :courses_users, :user_id, :string
rename_table :courses_users, :enrollments
end
def self.down
rename_table :enrollments, :courses_users
change_column :courses_users, :user_id, :integer
end
end
the queries Course.users and User.courses start failing (below from Rails console). User.courses returns an empty array (whereas before there are multiple Course objects), and Course.users throws an exception because of mismatched column types (which obviously makes sense):
u = User.take
User Load (0.9ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 1, username: "director", g_number: "g00000000", password_digest: "$2a$10$dvcOd3rHfbcR1Rn/D6VhsOokj4XiIkQbHxXLYjy5s4f...", created_at: "2016-01-06 01:36:00", updated_at: "2016-01-06 01:36:00", first_name: "Director", last_name: "", role: 0, registered: true>
2.1.5 :002 > u.courses
Course Load (0.9ms) SELECT "courses".* FROM "courses" INNER JOIN "enrollments ON "courses"."id" = "enrollments"."course_id" WHERE "enrollments"."user_id" = $1 [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
2.1.5 :003 > c = Course.take
Course Load (0.7ms) SELECT "courses".* FROM "courses" LIMIT 1
=> #<Course id: 12754, year: 2015, semester: 0, department: 7, course: 101, section: 1, name: "SPA 101 01 - Elementary Spanish I">
2.1.5 :004 > c.users
PG::UndefinedFunction: ERROR: operator does not exist: integer = character varying
LINE 1: ... "users" INNER JOIN "enrollments" ON "users"."id" = "enrollm...
I need to be able to join on enrollments.user_id = users.g_number. What do I need to do in order to change the user_id column to a string type in the Enrollment model/table, and still be able to do Active Record queries like User.courses and Course.users?
Try by specifying the foreign and primary keys in enrollments model, like this
belongs_to :user foreign_key: :user_id, primary_key: :g_number

How to arrange the order of columns in a database of a Rails application?

I recently added a :title column using Rails migration:
class AddTitleToMicroposts < ActiveRecord::Migration
def change
add_column :microposts, :title, :string
end
end
I noticed that it appear at the end when I do user.microposts: in the console:
=> [#<Micropost id: 1, content: "test", user_id: 1, created_at: "2012-01-25 15:34:30", updated_at: "2012-01-25 15:34:30", title: nil>]
Is there any way of arranging the order of the title columns? Say, placing it right before the :content column?
There is an :after option to insert the columns (no :before option unfortunately)
class AddTitleToMicroposts < ActiveRecord::Migration
def change
add_column :microposts, :title, :string, :after => :content
end
end
Rearrange them in the schema and do rake db:schema: load

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?

Activerecord association question: getting has_many :through to work

I'm building an app in Ruby on Rails, and I'm including 3 of my models (and their migration scripts) to show what I'm trying to do, and what isn't working. Here's the rundown: I have users in my application that belong to teams, and each team can have multiple coaches. I want to be able to pull a list of the coaches that are applicable to a user.
For instance, User A could belong to teams T1 and T2. Teams T1 and T2 could have four different coaches each, and one coach in common. I'd like to be able to pull the list of coaches by simply saying:
u = User.find(1)
coaches = u.coaches
Here are my migration scripts, and the associations in my models. Am I doing something incorrectly in my design? Are my associations correct?
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :login, :string, :default => nil
t.column :firstname, :string, :default => nil
t.column :lastname, :string, :default => nil
t.column :password, :string, :default => nil
t.column :security_token, :string, :default => nil
t.column :token_expires, :datetime, :default => nil
t.column :legacy_password, :string, :default => nil
end
end
def self.down
drop_table :users
end
end
class CreateTeams < ActiveRecord::Migration
def self.up
create_table :teams do |t|
t.column :name, :string
end
end
def self.down
drop_table :teams
end
end
class TeamsUsers < ActiveRecord::Migration
def self.up
create_table :teams_users, :id => false do |t|
t.column :team_id, :integer
t.column :user_id, :integer
t.column :joined_date, :datetime
end
end
def self.down
drop_table :teams_users
end
end
Here are the models (not the entire file):
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :coaches, :through => :teams
class Team < ActiveRecord::Base
has_many :coaches
has_and_belongs_to_many :users
class Coach < ActiveRecord::Base
belongs_to :teams
end
This is what happens when I try to pull the coaches:
u = User.find(1)
=> #<User id: 1, firstname: "Dan", lastname: "Wolchonok">
>> u.coaches
ActiveRecord::StatementInvalid: Mysql::Error: #42S22Unknown column 'teams.user_id' in 'where clause': SELECT `coaches`.* FROM `coaches` INNER JOIN teams ON coaches.team_id = teams.id WHERE ((`teams`.user_id = 1))
Here's the error in sql:
Mysql::Error: #42S22Unknown column 'teams.user_id' in 'where clause': SELECT coaches.* FROM coaches INNER JOIN teams ON coaches.team_id = teams.id WHERE ((teams.user_id = 1))
Am I missing something in my :through clause? Is my design totally off? Can someone point me in the right direction?
You can't do a has_many :through twice in a row. It'll tell you that its an invalid association. If you don't want to add finder_sql like above, you can add a method that mimics what you're trying to do.
def coaches
self.teams.collect do |team|
team.coaches
end.flatten.uniq
end
It's more of a many-to-many-to-even-more-relationship. I'd just write some sql:
has_many :coaches, :finder_sql => 'SELECT * from coaches, teams_users WHERE
coaches.team_id=teams_users.team_id
AND teams_users.user_id=#{id}'
I don't think ActiveRecord can handle doing a 2 step join in a has_many relationship. In order for this to work you'll have to join users to team_users to teams to coaches. The through option only allows for one extra join.
Instead you'll have to use the :finder_sql option and write out the full join clause yourself. Not the prettiest thing in the world, but that's how it goes with ActiveRecord when you try to do something out of the ordinary.
You could drop the "has_many :coaches, :through => :teams" line in users & then hand-write a coaches method in your User model like so:
def coaches
ret = []
teams.each do |t|
t.coaches.each do |c|
ret << c
end
end
ret.uniq
end
While I love to write SQL, I don't think it's the ideal solution in this instance. Here's what I ended up doing in the User model:
def coaches
self.teams.collect do |team|
team.coaches
end.flatten.uniq
end
def canCoach(coachee)
u = User.find(coachee)
coaches = u.coaches
c = []
coaches.collect do |coach|
c.push(coach.user_id)
end
return c.include?(self.id)
end
I thought about just doing it all in one fell swoop, but I liked the ability to return an array of coach objects from within the user object. If there's a better way to do it, I'm very interested in seeing the improved code.

Resources