one to one or zero association in rails - ruby-on-rails

Model I
class TimeLog < ActiveRecord::Base
has_one :custom_time_fields, :dependent => :destroy
end
Model II
class CustomTimeFields < ActiveRecord::Base
belongs_to :time_log
end
above design in terms of database will be
timelogs table + custom_time_field_id(foreign key)
custom_time_fields
So when i delete timelog entry its associated 'custom_time_field' will be auto deleted by rails
But i want database design like following
Table I:
time_logs
Table II
custom_time_fields (having time_log_id as foreign key)
Table I will have Zero or one association of Table II
How can i represent above database design in Rails models, so that when i delete time_log, associated custom_time_field entry is auto deleted.

You have to switch the has_one and belongs_to relations of your models to change the table containing the foreign key (the model with the relation belongs_to is the one holding the foreign key). Do not forget to adapt your migrations according to the change (to declare the time_log_id column).
I think the "zero or one" relation you're looking for is the has_one relation. This relation is not mandatory (unless you add a validation to it).

Related

setting up a belongs_to relation when the foreign key is stored in metadata

In a Rails 4 application, I have an STI model that stores metadata in a jsonb column.
Base Class:
class Post < ActiveRecord::Base
...
end
Subclass:
class JobPost < Post
# has a jsonb column for metadata
end
One of the data attributes in the metadata column of a JobPost is a foreign_key reference to another table (company_id). I'd like to add a belongs_to :company reference in the JobPost model. It seems like this should be possible by doing something like
class JobPost < Post
belongs_to :company do
Company.find_by_id self.metadata['company_id']
end
end
but that doesn't appear to work. Help?
Note: I am not necessarily intent on using belongs_to rather than writing needed methods like def company by hand, but I do need a way to eager load companies when listing job posts. If there's a way to do that eager loading without a belongs_to relation I'm all ears.
Update1
I have also tried the following, which doesn't appear to work either:
class JobPost < Post
belongs_to :company, foreign_key: "(posts.metadata->>'company_id')::integer".to_sym
end
Update2
To be more clear about my intentions and need:
1) A JobPost belongs_to a Company, but a Post (and other subclasses of Post) does not. I'd prefer not to jankily add the company_id column to the posts table when it won't be used by the other subclasses.
2) A JobPost could justify having it's own table (perhaps the relationship with a Company is enough to justify it). There are reasons why this wouldn't be ideal, but if that's the only answer I'm open to it. I'd, however, like a more definitive "what you're trying to do can't be done" response before going down this road, though.
The primary question is whether you can customize belongs_to so that it uses the metadata column rather than expecting the foreign key to be a column in the table.
The secondary question is whether you can eager load companies alongside job posts without having that belongs_to relation set up.
EDIT
UPD 2
You need to add "company_id" column to the base class of your STI table. If JobPost inherits from Post, and it should have "company_id" then add the "company_id" column to Post (base table).
Remember STI stands for "Single Table Inheritance" so there is only one table on database schema level. Imagine a column of a Post table, where few data records are the foreign key entries for Companies with company_id and what about the other records of this column with non JobPost subclass types, are they null/empty? Hence the relationship is defined with parent STI table and subclass inherits these relations. Additional type column in STI defines the subclass type.
Check here
You may need to dig further on Polymorphic classes instead of STI if both JobPost and Post have relationship with Company, else create two separate model, as they tend do have some unique relationships and column fields.
UPD
Based on updated ask
app/model/company.rb
class Company < ActiveRecord::Base
has_many :posts
delegate :jobposts, to: :posts
end
app/model/post.rb
class Post < ActiveRecord::Base
belongs_to :company
self.inheritance_column = :ptype
scope :job_posts, -> { where(ptype: 'JobPost') }
def self.ptype
%w(JobPost)
end
end
app/models/jobpost.rb
class JobPost < Post; end
Create a company
company = Company.create!(company_params)
Create some posts and add them to the company
company.posts << JobPost.new(jobpost_params)
To fetch jobpost by company relationship
company.job_posts
In case you are storing company_id in jsonb in any which column, just format your jobpost_params hash input accordingly and it should do the deed for you
OLD ASK
To find by primary key
Company.find(id)
In your case, id is self.metadata['company_id']
To find by other keys
Company.find_by(key: value)
Company.find_by_id is no more recommended
Please remove do and end after belongs_to in your model, instead in your controller you can write:
Jobpost.all.each do |x|
# your do
end
regarding foreign key, as rails is convention over configuration, it by default includes company_id reference to Jobpost which you can change in your Company.rb model

how to do a has_many relation on two tables?

I'm trying to do a has_many relation with a table on creation and also to add it in another table already created.
I have my User table already with no relations.
I want to create a Pro_user with a relation has_many User.
The thing is that one User can have multiple Pro_user and a Pro_user can also also have multiple User.
So both tables need a has_many right ?
So what I thought of was that
rails g model pro_user name:string email:string password_digest:string user:references
But this is not the right thing, it's doing a belongs_to from Pro_user to User
And also how should I do to do add the has_many on my existing table User ? Do I have to do a migration to recreate the table and adding the relation ?
Thanks for your help !
The recommended approach for a many to many association is the "has_many_through" approach. This allows you to add additional columns later on to the join table if you need more data. You'll need a join table that will at the least have two reference columns to your Users and ProUsers tables along with the standard id column.
(Refer to: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association)
So your User and ProUser tables will not have any reference columns in them. Instead you'll make a third table called BoatsAndPros (call it whatever you like) and do:
create_table :boats_and_pros do |t|
t.belongs_to :user, index: true
t.belongs_to :pro_user, index: true
t.timestamps
end
Then in the corresponding boats_and_pros.rb Model file you'll add:
belongs_to :user
belongs_to :pro_user
In your user.rb file you'll add:
has_many :boats_and_pros
has_many :pro_users, through: :boats_and_pros
In your pro_user.rb model file you'll add
has_many :boats_and_pros
has_many :users, through: :boats_and_pros
Two key takeaways are:
The "oldschool" has_and_belongs_to_many approach is still fine however doesn't allow room to grow like the has_many_through approach here and you'll need to specifically name the table pro_users_users because Rails expects the two tables to be listed in lexical order.
One-to-many relationships like what you ended up with on your original attempt keep the reference in one of the tables while many-to-many relationships require a third join table as shown above.

What is the use of join table in Rails?

For what purposes I'd potentially need to use join table?
For example, when I run rails g migration CreateJoinTable products suppliers, it creates the respective products_suppliers table with products_id and suppliers_id columns. Moreover, these fields have the option :null set to false by default. Why are those fields needed? What they are used for? And what is that null option?
Thanks in advance.
It's standard relational database functionality.
--
Rails is designed on top of a relational database (typically MYSQL or PGSQL), which basically means that you're able to reference "associated" data through the use of foreign keys:
In context of relational databases, a foreign key is a field (or collection of fields) in one table that uniquely identifies a row of another table
In the case of Rails, "relationships" in the database are maintained by ActiveRecord - an ORM (Object Relational Mapper). This means that from the application layer, you just have to focus on populating objects:
#user = User.find x
#user.products #-> outputs records from "products" table with foreign key "user_id = x"
ActiveRecord manages your associations, which is why you have to define the belongs_to / has_many directives in your models etc.
Most associations you create will be reference other tables directly:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :products
end
#app/models/product.rb
class Product < ActiveRecord::Base
belongs_to :user
end
The problem with this is that it only allows you to associate single records; if you wanted to associate multiple (many-to-many), you need a join table.
Rails uses join tables for has_many :through and has_and_belongs_to_many relationships...
Join tables are populated with (at least) the primary key & foreign key of a relationship. For example...
user_id | product_id
1 | 3
1 | 5
2 | 3
This allows you to call:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
#app/models/product.rb
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
#user.products #-> all products from join table
#product.users #-> all users from join table
In short, if you want to have has_many <-> has_many associations, the join table is necessary to store all the references to the relative foreign keys.

has_one not updating attribute id?

So I have a DB relationship where a User has a PreferenceList. I have set up a preference_list_id in the database table, and I have set up a belongs_to :user relationship with PreferenceList and a has_one :preference_list relationship with User.
Here's the weird part. If I do user.preference_list = preference_list, I can access user.preference_list and it will give me the correct instance of preference_list. However, if I do user.preference_list_id (a valid, column, I've checked), it gives me nothing even after I have added preference_list to user.preference_list. I need the id to be updated for various database operations. What am I doing wrong?
The foreign key goes on the table for the class declaring the belongs_to association. So if you have
class PreferenceList < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :preference_list
end
table preferences_lists should have user_id columns. Rails will handle this columns properly. You can add preference_list_id column to users table as well. But this column has no special meaning, Rails will do nothing with it by default. Try to change belongs_to/has_one sides if you really need to operate preference_list_id. Or use user.preference_list.id

ActiveRecord two way foreign key.

I'm using some open gov data in MYSQL which I've imported into my rails App.
I've extended the database with some of my own tables, and use the rails provided column ids in my tables.
However, the tables of the original database are linked through a unique 'ndb' id.
I thought that by cross-referencing two :foreign_key=>'ndb' between the models I would get the tables linked properly, but through :foreign_key, it appears to be linking the id from one table to the ndb column of another.
My models look like this
class Food < ActiveRecord::Base
has_many :weights, :foreign_key=>'ndb'
has_many :food_names
end
class Weight < ActiveRecord::Base
belongs_to :food, :foreign_key=>'ndb'
Is there a way to specify that the 'ndb' column is the link between the food and weights table, and not the food_id to ndb?
Have you tried set_primary_key 'ndb' in your AR classes?
set_primary_key docs.
What I've done as a possibly temporary solution, is to used the :finder_sql to link the tables together.
My Food model now has:
class Food < ActiveRecord::Base
has_many :weights, :finder_sql =>'Select weights.* FROM weights LEFT JOIN foods ON foods.ndb=weights.ndb WHERE foods.id=#{id}'
I'm not sure if this is the best solution or not, but it seems to be working.

Resources