Rails - namespaced migrations - ruby-on-rails

I have the following model and migration:
class Content::Panels::Iframe < Content::Panel
## Associations ##
belongs_to :panel_holder, polymorphic: true
## Validations ##
validates :uri, presence: true
## Methods ##
def self.plural_name
'iframe_index'
end
end
class AddHeightToIframes < ActiveRecord::Migration[5.1]
def change
add_column :iframes, :height, :integer, after: :headline
end
end
The migration fails as there is no table called 'iframes'. After googling I've tried adding to the iframe class:
self.table_name_prefix = 'content_panels_'
self.table_name = 'content_panels_iframes'
and changing the table title to 'content_panels_iframes', However neither of these work when tried independently or together.
What am I doing wrong? Thanks in advance

When the table name is content_panels_iframes, the migration should be:
add_column :content_panels_iframes, :height, :integer, after: :headline

Related

RAILS - CHANGE FIELD OF ANOTHER TABLE - BOOLEAN

Good afternoon. I'm new to rails and I'm using google translate to post in English here, so sorry if it's not very readable.
My question is, I have a User table, and a Setting table.
They are related (but I don't know if the relationship is correct), they can even confirm me, and I would like to know if:
when creating a user, I would like to automatically change the "email" and "push" fields of that user's settings table to true.
Would it be possible via a method that in the user model called: "setting_default"?
User model.
class User < ApplicationRecord
has_one :setting
before_save :setting_default
def setting_default
self.setting.update(:email, 'true')
self.setting.update(:push, 'true')
end
Setting Model
class Setting < ApplicationRecord
has_one :user
end
The Controller is normal, if you need it, I can put it in the post
My migration:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
class AddSettingsToUser < ActiveRecord::Migration[6.0]
def change
add_reference :users, :setting, null: true, foreign_key: true
end
end
Google translate has worked well for you here.
First off you'll want to change your Setting model to belong to the User:
class Setting < ApplicationRecord
belongs_to :user
end
Your settings DB table is missing a user_id field to tie the setting back to the user. I'm not used to the add_reference technique so I just do things myself in the migrations. This would work:
class CreateSettings < ActiveRecord::Migration[6.0]
def change
create_table :settings do |t|
t.integer :user_id
t.boolean :email, default: true
t.boolean :push, default: true
t.timestamps
end
end
end
(Make note that your users DB table has a field setting_id that it does not need. I don't think it should be there. I would remove it. Unless it's a Rails 6 thing I'm not used to.)
Next it would probably be better to assign the values if the save succeeds (and not if it fails) so you'll want an after_save instead. And I'm simplifying your value assignment just in case you're having an issue there:
class User < ApplicationRecord
has_one :setting
after_save :setting_default
def setting_default
setting.email = true
setting.push = true
setting.save(validate: false)
end
private :setting_default
And to answer what seems to be your question, yes, what you're trying to do should be easily possible. This is a very common thing to do. It should work.
When you use one-to-one association you need to choose has_one in one and belongs_to in another model
Semantically user has one setting, but not setting has one user
So it's better to reverse them
To change your schema you need to write new migration
class ChangeOneToOneDirection < ActiveRecord::Migration[6.0]
def up
change_table :settings do |t|
t.belongs_to :user, foreign_key: true, null: false
end
User.where.not(setting_id: nil).find_each |user|
Setting.find(user.setting_id).update_columns(user_id: user.id)
end
change_table :users do |t|
t.remove :setting_id
end
end
def down
add_reference :users, :setting, null: true, foreign_key: true
Setting.find_each do |setting|
User.find(setting.user_id).update_columns(setting_id: setting.id)
end
change_table :settings do |t|
t.remove :user_id
end
end
end
After migration you can change User model
class User < ApplicationRecord
has_one :setting
after_commit :setting_default
private
def setting_default
setting&.update(email: true, push: true)
end
end
It's better to update associated model only if saves are in the database. And user can haven't setting. That's why after_commit and safe-navigator &

Rails model foreign key validation

I need to validate the existence of a row referenced by a foreign key in one of my models. The situation is like this:
Project.rb
class Project < ActiveRecord::Base
has_one :project_category
# -----------------------------------------------------------------
# this does not work because the attribute is actually called
# 'category_id' instead of the rails expected 'project_category_id'
# -----------------------------------------------------------------
validates :project_category, presence: true
end
Project Migration
class CreateProjects < ActiveRecord::Migration
def change
create_table :projects do |t|
# ----------------------------------------------
# this is why the column is called 'category_id'
# ----------------------------------------------
t.references :category, references: :project_categories, null: false
# all of my other fields here, unimportant
end
add_foreign_key :projects, :project_categories, column: :category_id
end
end
I know that I can write a custom validation method to check if the :category_id exists in the project_categories table but I would prefer to let rails handle the validation if there is a way, so I can keep my code DRY.
EDIT
ProjectCategory.rb
class ProjectCategory < ActiveRecord::Base
belongs_to :project
validates :name, presence: true, uniqueness: { case_sensitive: false }
end
ProjectCategory Migration
class CreateProjectCategories < ActiveRecord::Migration
def change
create_table :project_categories do |t|
t.string :name, null: false
end
end
end
It appears you only need to add the foreign_key option to your has_one declaration in order to specify the custom column name you've specified i.e. category_id instead of project_category_id. See Options for has_one for details.
# app/modeles/project.rb
class Project < ActiveRecord::Base
has_one :project_category, foreign_key: 'category_id'
# -----------------------------------------------------------------
# this does not work because the attribute is actually called
# 'category_id' instead of the rails expected 'project_category_id'
# -----------------------------------------------------------------
validates :project_category, presence: true
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

Mechanize mass assignment error for HABTM join table

The problem is that I get this error:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: amenity_id
when I run this code:
task import_amenities: :environment do
agent = Mechanize.new
Kindergarten.find_all_by_public(false).each do |k|
p = agent.get(k.uri)
amenities = p.search("td td tr:nth-child(11) td:nth-child(2)").text.split(/(;|,) */)
amenities.each do |a|
am = Amenity.find_or_create_by_name!("#{a}")
k.update_attributes(amenity_id: am.id)
end
end
end
Kindergartens and Amenities are linked through a HABTM relation and are are defined as below:
kindergarten.rb
class Kindergarten < ActiveRecord::Base
attr_accessible :location, :name, :public, :uri, :address, :contact,
:phone, :url, :email, :description,
:password, :password_confirmation, :amenity_ids
has_and_belongs_to_many :amenities
end
amenity.rb
class Amenity < ActiveRecord::Base
attr_accessible :name, :kindergarten_ids
has_and_belongs_to_many :kindergartens
end
and here's the migration for the join table:
class CreateKindergartensAmenitiesJoinTable < ActiveRecord::Migration
def up
create_table :kindergartens_amenities, :id => false do |t|
t.integer :kindergarten_id
t.integer :amenity_id
end
end
end
The error is caused by this line in the rake task:
k.update_attributes(amenity_id: am.id)
Everything seems to work great in the console until I reach the mass assignment. And I think i am really messing something up here since I've never used before HABTM.
Any thoughts?
I couldn't sleep last night because of this bug but I finally found the solution.
there are a few issues in the code and the first one i noticed once i started digging and adding data in the db manually is that the join table is wrongfully named. Fix for that:
class RenameKindergartensAmenitiesTable < ActiveRecord::Migration
def up
rename_table :kindergartens_amenities, :amenities_kindergartens
end
end
apparently the habtm association is has to have stuff put alphabetically in title. source
Second problem is that I assumed that
k.amenity_id = am.id
would add an amenity_id / kindergarten_id for each amenity existing. In fact k.amenity_id does not mean anything (especially in the case of many ids). The solution that worked is this:
amenities.each do |a|
am = Amenity.find_or_create_by_name!("#{a}")
k.update_attributes(amenity_ids: k.amenity_ids.push(am.id))
end
I haven't modified the attr_accessible anywhere

rails object.association.count postgres error

I just changed my DB from mysql to postgres and I'm getting the following error:
ActionView::Template::Error (PG::Error: ERROR: operator does not exist: character varying = integer
LINE 1: ...ELECT COUNT(*) FROM "agents" WHERE "agents"."client_id" = 1
when doing
client.agents.count
I have a Data is structured as follows: Clients have several Agents, and can only add more Agents if agents.count < X, so I'm using something like client.agents.count to retrieve this value and compare, but I'm getting that error. Do I need to use manual sql to get this done? Or am I missing something stupid?
Thank you for your comments
MODEL INFO
class Agent < User
belongs_to :client
attr_accessible :client_id
validates :client_id, presence: true
end
class Client < User
attr_accessible :appId, :expire_date, :legacy, :url, :plan_id, :chat_window_color, :chat_head_color, :chat_box_container_color, :chat_box_color, :tab_message, :greeting, :please_wait_message, :send_message_button, :comments_label, :offline_message
belongs_to :plan
has_many :agents, :dependent => :destroy
has_secure_password
after_initialize :init
#omited validations
private
#BEGIN PRIVATE METHODS
end
Both inherit from user
class User < ActiveRecord::Base
self.abstract_class = true
attr_accessible :email, :name, :password, :password_confirmation
attr_accessor :updating_password
has_secure_password
before_save { self.email.downcase! }
#the controller must set updating_password to FALSE to avoid validation
def should_update_password?
updating_password || new_record?
end
end
So I found the issue, the column client_id is a varchar and mysql allowed this but postgres complained about the different datatypes. Got a mgiration working by doing something like this:
def up
rename_column :agents, :client_id, :client_id_old
add_column :agents, :client_id, :integer
Agent.reset_column_information
Agent.find_each { |c| c.update_attribute(:client_id, c.client_id_old) }
remove_column :agents, :client_id_old
end
From this link How do I change column type in Heroku?.
To avoid the issues when changing datatypes in postgres directly with change_column. Hope this helps someone else

Resources