Rails Relational Database not working how I want - ruby-on-rails

In the console
a = Reported.new
This works. After tinkering.
a.profile = Profile.first
But it's not what I want! I want a.profile to even exist. I want a.reported_by to be a profile! And I want a.reported to be a profile!
Again what I want is
a.reported_by = Profile.last #or any such profile
a.reported = Profile.first #or any such profile
Model
class Profile < ActiveRecord::Base
has_many :reported, dependent: :destroy
Migration
It doesn't have a reported column, I am not sure about the right way to implement that either.
class CreateReporteds < ActiveRecord::Migration
def change
create_table :reporteds do |t|
t.belongs_to :profile
t.integer :reported_by
t.string :reason
t.timestamps
end
end
end

Your migration seems... off. I've never seen t.belongs_to :something in a migration - shouldn't it be t.integer :profile_id? (I couldn't find documentation supporting the belongs_to syntax there).
If you want Reported#reported_by to return a Profile, then you need a reported_by_id integer on it, NOT a reported_by integer. Rails has a convention where you should make your referenced objects (in this case, a belongs_to :reported_by relationship) use the relationship_id format for it's foreign key.
Then you should have this in your class:
class Reported < ActiveRecord::Base
belongs_to :reported_by, class_name: "Profile"
end
This will make it so it uses reported_by_id as the foreign key for a Profile object, but return it as Reported#reported_by.
Then:
class Profile < ActiveRecord::Base
has_many :reporteds, foreign_key: 'reported_by_id'
end
Should let you do Profile#reporteds
And your migration would look like this:
class CreateReporteds < ActiveRecord::Migration
def change
create_table :reporteds do |t|
t.integer :reported_by_id
t.string :reason
t.timestamps
end
end
end

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 &

How to set foreign key in Rails

I Have a model named employee. The following is my migration file.
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees, id: false do |t|
t.string :name
t.string :password
t.string :role
t.primary_key :name
end
end
end
Now, I want to create a model named "teamplayer" with the columns as 'name' which needs to refers 'name' column in employee model. And 'tl' column
which is independent to this model. The following is my "teamplayer" migration file.
class CreateTeamplayers < ActiveRecord::Migration
def change
create_table :teamplayers, :id false do |t|
t.string :tl
t.string :name
end
end
end
In the above file, how to reference 'name' column to the model employee? So how to achieve foreign key in rails.
I think you want to look into Active Record Associations (http://guides.rubyonrails.org/association_basics.html)
I know you've asked to create a foreign key on name but unless you plan to ensure that name is unique, then this is possibly not the best plan (depending on the actual relationship you are trying to model - one to many / one to one etc).
I would be tempted to set up the foreign key relationship on employees.id. To do this, you can use the has_many and belongs_to associations.
You could change your teamplayers migration as follows:
class CreateTeamplayers < ActiveRecord::Migration
def change
create_table :teamplayers, :id false do |t|
t.belongs_to :employee
t.string :tl
end
end
end
Then in your Employee model, you can add the has_many side of things:
class Employee < ActiveRecord::Base
has_many :teamplayers
end
You can still easily get the Employee name given a Team Player record with a simple join.
Edit - to get the Employee, you can do something like this, assuming #tis a teamplayer instance:
#t.employee.name
(the code is untested and from memory so....)
You can do it in the teamplayer model, you just need to add index in your migration
class CreateTeamplayers < ActiveRecord::Migration
def change
create_table :teamplayers, :id false do |t|
t.string :tl
t.string :name
end
add_index :teamplayers, :name
end
end
You can set name as Primary key inside the employee model like this
class Employee < ActiveRecord::Base
self.primary_key = "name"
has_many :teamplayers
end
Now inside the model Teamplayer you can set the foreign key
class Teamplayer < ActiveRecord::Base
belongs_to :employee, foreign_key: 'name'
end
This should reference 'name' to employee model

Associating two models in a way both models' attributes are always pulled together in any view

I'm building a tech-specific pricegrabber-like web app, and I have a model that carries params that are common in all products. This model is called Product. Then I have one model for each type of product that I'm going to work with, for example, I'm now trying to build the first specific model, which is Videocard. So, the Product model always must have one Specific model, in this case Product-Videocard.
At this moment I'm stuck finding a way to make a product and a specific model always come tied together whenever I reach to them, be it in an index view, show view, form_for, a search, etc. But I can't picture in my head how a form will create an item and its specifications and insert a foreign key into another model with only one submit request.
Below are both models and the migrations for each:
class Product < ApplicationRecord
#belongs_to :productable, :polymorphic => true
has_one :videocard, dependent: :destroy
# Comment for this Stackoverflow question: the way I'm thinking I
# should have to make tons of has_one associations, for the other
# products. Is there a DRY way to do this?
has_many :prices, through: :stores
validates :platform, presence: { message: "should be specified." }
validates :name, presence: { message: "should be specified." }
validates_associated :videocard
end
class Videocard < ApplicationRecord
belongs_to :product
end
Migrations (shortened to make this question as clear as possible):
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.references :productable, polymorphic: true, index: true
t.string :name
t.string :image
t.string :partnum
t.string :manufacturer
t.string :platform #mobile, desktop, server, laptop
t.timestamps
end
end
end
class CreateVideocards < ActiveRecord::Migration[5.0]
def change
create_table :videocards do |t|
t.references :product, index: true
t.integer :memory
t.string :interface
# [...lots of attributes...]
t.integer :displayport
t.integer :minidisplayport
t.integer :tdp
t.timestamps
end
end
end
Also how can I make it so that Product only needs one has_one association, instead of using multiple ones. Remember that Videocard will have one type of specification, Memory will have other, and so on.

Is it advisable to use :foreign_key in my migrations rather than just adding user_id?

I am using rails 4.2, I just want to know if there would be any difference if I use the :foreign_key keyword in my migrations rather than just adding a user_id column to add relationship to my models ?
YES
The key difference is not on the application layer but on the database layer - foreign keys are used to make the database enforce referential integrity.
Lets look at an example:
class User < ActiveRecord::Base
has_many :things
end
class Thing < ActiveRecord::Base
belongs_to :user
end
If we declare things.user_id without a foreign key:
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.integer :user_id
t.timestamps null: false
end
end
end
ActiveRecord will happily allow us to orphan rows on the things table:
user = User.create(name: 'Max')
thing = user.things.create
user.destroy
thing.user.name # Boom! - 'undefined method :name for NilClass'
While if we had a foreign key the database would not allow us to destroy user since it leaves an orphaned record.
class CreateThings < ActiveRecord::Migration
def change
create_table :things do |t|
t.belongs_to :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
user = User.create(name: 'Max')
thing = user.things.create
user.destroy # DB says hell no
While you can simply regulate this with callbacks having the DB enforce referential integrity is usually a good idea.
# using a callback to remove associated records first
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
end

Rails: incrementing attribute from a model, upon creation of an instance from another model

Our Rails app works with the following models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are our migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
...
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
When a new #calendar is created, we need to increment :total_calendar_count and :owner_calendar_count by one in the User table.
We tried this in the CalendarsController:
class CalendarsController < ApplicationController
def create
#calendar = current_user.calendars.create(calendar_params)
current_user.total_calendar_count += 1
current_user.owned_calendar_count += 1
current_user.administrations.find_by(calendar_id: #calendar.id).update(role: 'Creator')
...
end
But it does not seem to update :total_calendar_count and :owner_calendar_count by one in the User table.
Are we missing a step here? Should we use an update action instead?
The actual problem in your code is that you don't then save the user.
So you update the counter... but this changes it on the local instance... and then after the controller action is done the change you made just disappears.
if you wanted to keep your code the way it is, you could do:
current_user.save
at the end.
but I'd advise you to look into the counter_cache, because it's the Rails way.
Also I'll point out that you haven't checked that the calendar successfully got created, before incrementing that counter... it's possible that it could fail a validation and not really have been created... you need to check for that first.
I have a best idea to solve your problems is as below....
Create a method that will call on the creating of calendar with the callbacks of model like as below...
Add the below inside the calendar model just after the validation and ORM relations
after_create :increment_counter
def increment_counter
calendar_user = self.user
calendar_user.update(:total_calendar_count += 1, :owned_calendar_count += 1 )
end
With the above code you don't need to do anything. It will increment the counter of calendar on every new entry of calendar.

Resources