I am trying to make a RoR project and I am currently have issues with Model Association. This is probably caused by an error or misunderstanding but in the last 2 days I have not been able to find this error or misunderstanding.
I want the associations to work like this:
User
=> gapiToken
=> userSession
So that technically I could just call User.find(foo).gapiToken.
Currently I have it setup so
User
has_many :userSessions
has_one :gapiToken
UserSession
belongs_to :user
GapiToken
belongs_to :user
However for some reason this does not seam to be working.
For example, this piece of code:
#user = User.create(gid: foo, permissions: bar)
#gapiToken = #user.gapiToken.create(access_token: foo, token_type: bar, expires_on: bazz, refresh_token: bop)
#^ Error ^ "undefined method `create' for nil:NilClass"
Am I going about this wrong in the usage or in the setup, or both?
Complete code
First, be sure to follow conventions. Use under_scores, not camelCase:
User
has_many :user_sessions
has_one :gapi_token
Second, the method #model_instance.association.create is for one-to-many associations, not one-to-one associations. It should be:
#user.create_gapi_token(...)
See here for more information about the associations API.
A note on YOUR CODE
Don't forget indexes.
A basic rule of thumb: index foreign keys, and index both keys on a join table. Example:
create_table :user_sessions do |t|
t.belongs_to :user # will result in t.integer :user_id
end
add_index :user_sessions, :user_id
create_table :gapi_tokens, id: false do |t|
t.belongs_to :user
end
add_index :gapi_tokens, :user_id
Example of an index on a join table (note id: false and unique: true):
create_table :users_favourites, id: false do |t|
t.belongs_to :user
t.belongs_to :favourite
end
add_index :users_favourites, [:user_id, :favourite_id], unique: true
Read about migrations, then read and re-read the Rails guides.
It's important you understand the conventions so you don't shoot yourself in the foot later down the road.
Related
recently I have a migration that adds a user_id column to the watch_events tables. and thus I want to change the watch_event models to handle belongs_to but with multiple approach
create_table 'users', force: :cascade do |t|
t.integer 'id'
t.integer 'customer_id'
end
create_table 'watch_events', force: :cascade do |t|
t.integer 'customer_id'
t.integer 'user_id'
end
previously
class WatchEvent < ApplicationRecord
belongs_to :user, foreign_key: :customer_id, primary_key: :customer_id
end
what I want:
if watch_event.customer_id is present, i want to use belongs_to :user, foreign_key: :customer_id, primary_key: :customer_id
if watch_event.customer_id is not present, i want to use normal belongs_to :user
how can I achieve this on the watch_event model?
I do not think that Rails supports 'fallback foreign keys' on associations. However, you can write a simple wrapper for your problem. First, relate your WatchEvent class twice to the user model, using your two keys and two 'internal' association names (:user_1 and :user_2). Then, add a 'virtual association reader' (user) and a 'virtual association setter' (user=(new_user)):
class WatchEvent < ApplicationRecord
belongs_to :user_1,
class_name: 'User',
foreign_key: :customer_id
belongs_to :user_2,
class_name: 'User',
foreign_key: :user_id
def user
user_1 || user_2
end
def user=(new_user)
self.user_1 = new_user
end
end
With this solution, the requirements "use customer_id to find user" and "use user_id as fallback foreign key if customer_id is nil or doesn't yield a result" is satisfied. It happens in the association reader method user. When there is a reader method, you'll need a setter method, which is user=(). Feel free to design the setter's internals as required, mine is just a suggestion.
BTW: You may need to re-add the declaration of the foreign primary_key. I omitted that for clarity.
If I understand your question correctly, then what you are looking for is a Polymorphic association.
If you see the code below, what it basically does is create two columns in the watch_events table, watcher_type and watcher_id. And the belongs_to :watcher then uses the watcher_type column to identify which model it should associate to,
create_table 'watch_events', force: :cascade do |t|
t.references 'watcher', polymorphic: true, null: false
end
class WatchEvent < ApplicationRecord
belongs_to :watcher, polymorphic: true
end
ich would like to ask for some help with has_and_belongs_to_many association.
I have the following tables and models:
candidate_job_title_translations -> Candidate::JobTitleTranslation (in a subfolder with table_name_prefix )
create_table "candidate_job_title_translations", force: :cascade do |t|
end
profile_experiences, ProfileExperience
create_table "profile_experiences", id: :serial, force: :cascade do |t|
end
candidate_job_title_translations_profile_experiences, No model
create_table "candidate_job_title_translations_profile_experiences", id: false, force: :cascade do |t|
t.bigint "candidate_job_title_translation_id", null: false
t.bigint "profile_experience_id", null: false
end
The two models are setuped for the association:
class ProfileExperience < ApplicationRecord
has_and_belongs_to_many :candidate_job_title_translations, class_name: 'Candidate::JobTitleTranslation'
end
class Candidate::JobTitleTranslation < ApplicationRecord
has_and_belongs_to_many :profile_experiences, class_name: 'ProfileExperience'
end
My Problem now is, I get a ActiveRecord error, saying job_title_translation_id does not exist, which is correct. It should look for candidate_job_title_translation_id
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column candidate_job_title_translations_profile_experiences.job_title_translati
on_id does not exist
LINE 1: ...ces" ON "candidate_job_title_translations"."id" = "candidate...
I have the feeling I can solve it by not having the table_name_prefix and model structure but, that is not good in terms of my structure.
Maybe you have an idea.
Thanks
Thats not really a good domain model to start with.
If you want a translations table you want to do it something like:
class Position
belongs_to :title
has_many :translated_titles,
through: :title,
source: :translations
end
class Title
has_many :positions
has_many :translations,
class_name: 'Titles::Translation'
end
class Titles::Translation
belongs_to :title
end
You should be more concerned about creating meaningful relations and duplication than "I don't want to have another class, waaah" which is the most common reason for choosing HABTM.
Also when "namespacing" models in Rails the module should be plural:
Good: Titles::Translation
Bad: Title::Translation
This convention is due to the way that ActiveRecord maps tables to tables to classes and the fact that nesting your model inside another model class is not really a good idea.
Versions: CENTOS7, mysql2('>= 0.3.13', '< 0.5'), rails('4.2.6')
index.html.erb
<% #sections.each do |section| %>
<tr>
<td><%= section.course_id %></td>
<td><%= section.term_id %></td>
<td><%= section.user_id %></td>
</tr>
<% end %>
sections controller
class SectionsController < ApplicationController
before_action :authenticate_account!, except: [:show]
def index
#sections = User.find_by_account_id(current_user).courses
end
def show
end
end
createSections migration
class CreateSections < ActiveRecord::Migration
def change
create_table :courses do |t|
t.integer :course_id
t.timestamps null: false
end
create_table :terms do |t|
t.integer :term_id
t.timestamps null: false
end
create_table :users do |t|
t.integer :user_id
t.timestamps null: false
end
create_table :sections do |t|
t.belongs_to :course, index: true
t.belongs_to :term, index: true
t.belongs_to :user, index: true
t.timestamps null: false
end
end
end
course.rb model
belongs_to :user
has_many :sections
has_many :terms, :through => :sections
term.rb model
belongs_to :user
has_many :sections
has_many :courses, :through => :sections
section.rb model
belongs_to :course
belongs_to :term
belongs_to :user
user.rb
has_many :sections
has_many :courses, :through => :sections
has_many :terms, :through => :sections
Expected result: List the current(logged in) user's courses/terms/ID
Current result: blank
This is my first time working with rails and SO, I tried changing the relationships a few times to see if anything would change but not sure how to approach this. I have tried using ActiveRecord:Associations as a reference. What do I need to do to make this work?
If you already have current_user then you can just call the related models directly.
#sections = User.find_by_account_id(current_user).courses
to
#sections = current_user.courses
The first thing to address is that you typically want separate migrations for each distinct thing that you're migrating. It's a convention that helps you keep fine-grained control over your changes, and it helps keep your migrations clean.
Sometimes, though, you actually want to have a "mass" migration. You have a mass-migration here. In the mass-migration circumstance, the migration name should reflect the combined purpose, so you'd want to name it something like CreateCoreTables; CreateSections is too narrow a name for creating multiple tables. You will also need to change the name of the migration file to be 2016XXXXXXXX_create_core_tables.rb, where XXXXXXXXX is left as the previous value.
Next, you'll want to correct your use of keys (the _id columns), as these are improperly declaring the association fields, which will cause the associations to not work (or work incorrectly).
Instead, you want something like this:
class CreateCoreTables < ActiveRecord::Migration
def change
create_table :courses do |t|
t.timestamps
end
create_table :terms do |t|
t.timestamps
end
create_table :users do |t|
t.timestamps
end
create_table :sections do |t|
t.integer :user_id, null: false
t.integer :term_id, null: false
t.integer :course_id, null: false
t.timestamps
end
end
end
It's difficult to tell what the actual intended relationships are from the code provided. It would be worth reading on Active Record Migrations to make sure that you understand what relationships you intend, and how to describe them. While you're working out the migrations, keep revisiting the model relationships, as well. These are the bedrock of the application, so you want to spend time getting them right.
Remember, for ActiveRecord relationships, these rules will guide you:
If a model owns another model, use has_many or has_one
In the owned model, use belongs_to
If a model needs access to another model (but doesn't own it), use a has_many, :through relationship
Models cannot have has_many or has_one relationships directly to each other; you need an intermediate table in that case with the requisite belongs_to for each of the other tables
Once you have the migrations and relationships worked out, you can move on to the query. You can use Rails Eager Loading to optimize the query to retrieve the associations at the same time. This will address your current functional needs, and prevent an N+1 query issue at the same time.
#sections = Section.joins(courses: :terms).where(user: current_user)
When you have retrieved #sections, you can do these types of actions to get the data that you want. The courses and terms members are collections, and you can interact with them as though they were arrays:
#sections.each do |section|
puts "Section: #{section.id}"
puts "Number of user sections: #{#sections.courses.length}"
section.courses.each do |course|
puts "Course: #{course.id}"
end
puts "Number of user terms: #{#sections.terms.length}"
#sections.terms do |term|
puts "Term: #{term.id}"
end
puts "User's email: #{#sections.user.email}"
end
Once you've mastered these, you've got the basics of Rails. Work on one model/controller at a time to keep from overcomplicating the work; you can always add on more once once component is working like you expect. Always make sure that you have your foundation working before you move onto new aspects of the app that will depend on it.
Also, remember to use the Rails guides. They're very helpful, so keep them on hand at all times while you're learning. SO is also a great resource, and make sure that you ask pointed questions, so that you can get direct answers.
I have been trying and failing for 2 days now :) to get a list of ideas (posts basically) with likes. Order Desc preferably.
I have scaffolded ideas and users which work fine.
Likes (socialization gem) gives me the headache.
I can add likes and retrieve them. And I can also find out how many likes a specific idea has: idea.likers(User).count
and find out whether a user likes a specific idea: user.likes?(idea)
But I can't do agregates because of the non-standard field names which prohibit me from making a JOIN.
create_table "likes", force: :cascade do |t|
t.string "liker_type"
t.integer "liker_id" (this is/should be user_id)
t.string "likeable_type"
t.integer "likeable_id" (this is/should be idea_id)
t.datetime "created_at"
end
add_index "likes", ["likeable_id", "likeable_type"], name: "fk_likeables"
add_index "likes", ["liker_id", "liker_type"], name: "fk_likes"
Models:
like.rb - empty
user.rb - acts_as_liker
idea.rb - acts_as_likeable
Is there a way to join likes and ideas eg somehow matching liker_id to user_id? Or shall I rename the fields in the table (liker_id to user_id and likeable_id to idea_id)...? And also add these:
like.rb
belongs_to :user
belongs_to :idea
idea.rb
has_many :likes, dependent: :destroy
user.rb
has_many :likes, dependent: :destroy
Thanks in advance!
To specify a different column as foreign key which gets used in joins, you could add foreign_key: ... option to belongs_to as follows:
# app/models/like.rb
belongs_to :user, foreign_key: :liker_id
belongs_to :idea, foreign_key: :likeable_id
See referenced documentation on belongs_to.
You can also specify join conditions yourself as follows:
Idea.joins('inner join likes on ideas.id = likes.likeable_id').where(...)
I've been trying to setup a Single Table Inheritance model in Rails 3 in which the parent class also contains a has_many relationship. Unfortunately I can't get it to work. Here are three classes as an example:
class Article < ActiveRecord::Base
has_many :paragraphs, :dependent => :destroy, :autosave => true
end
class Paragraph < ActiveRecord::Base
belongs_to :article
end
class SportsArticle < Article
end
And here's the migration that would be used to set this up:
class AddTables < ActiveRecord::Migration
def self.up
create_table :articles do |t|
t.string :type, :null => false # for STI
t.string :title, :null => false
t.timestamps
end
create_table :paragraphs do |t|
t.references :article, :null => false
t.timestamps
end
end
def self.down
drop_table :articles
drop_table :paragraphs
end
end
When I set it up this way and I try to create a new SportsArticle, say by doing the following:
SportsArticle.create(:title => "Go Giants")
I always get the following error:
"TypeError: can't convert String into Integer"
I have no idea how to fix this issue and have tried finding a solution online to no avail. Does anybody who has experience with STI models see anything wrong? Here's the link to the documentation on the create method if it will help in diagnosing the problem:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-c-create
Try renaming :type to something else, like :article_type
eg:
t.string :article_type, :null => false # for STI
The error was being caused due to a naming collision. I was using a name for one of my models called "attributes" which was causing the problem. The hint that eventually diagnosed the problem came from the Rails Association Documentation.