Seeding a database with "flights" - ruby-on-rails

I'm trying to seed my database and I keep getting the error "ActiveRecord::RecordInvalid: Validation failed: Arriving flight must exist". In my method to create the association in my seeds.rb file I supply the arrival_airport_id so I'm not sure what the problem is.
seeds.rb
Airport.delete_all
Flight.delete_all
#Airport seeds
airports = [
["Boston Logan International Airport", "BOS"],
["Gulfport", "GPT"],
["Jackson", "JAN"],
["Charleston", "CRW"]
]
airports.each do |full_name, name|
Airport.create!( full_name: full_name, name: name )
end
a = Airport.all[0..1]
b = Airport.all[2..3]
a.each_with_index do |a, index|
a.departing_flights.create!(
arrival_airport_id: b[index]
)
end
Airport model:
class Airport < ApplicationRecord
has_many :departing_flights, class_name: "Flight", foreign_key: "departing_airport_id"
has_many :arriving_flights, class_name: "Flight", foreign_key: "arrival_airport_id"
end
Flight model:
class Flight < ApplicationRecord
belongs_to :departing_flight, class_name: "Airport", foreign_key: "departing_airport_id"
belongs_to :arriving_flight, class_name: "Airport", foreign_key: "arrival_airport_id"
end

This is a common mistake and there are two fixes.
a.each_with_index do |a, index|
a.departing_flights.create!(
arrival_airport_id: b[index] # This line is the problem
)
end
You're assigning an object to an id column. You can either assign the id to the id column or the object to the object column.
arrival_airport_id: b[index].id
# or
arrival_airport: b[index]
Rails tries to help you out the best it can but you have to give it the right object types.

Related

How to display Parent record with total number of its Child record in rails

class Attachment < ActiveRecord::Base
belongs_to :user, foreign_key: :creator_id
belongs_to :deal_task, foreign_key: :relation_id
end
class DealTask < ActiveRecord::Base
has_many :attachments, foreign_key: :relation_id
end
I have parent table called DealTask and child table called Attachment
I want a list of DealTask records with associated total number of attachments
DealTask.all.map do |deal_task|
deal_task.
attributes.
with_indifferent_access.
slice(:id, :name).
merge!(total_attachment: deal_task.attachments.count)
end
Or, if you don't care about indifferent access and you don't mind having all the DealTask attributes, you can write this with on a single line:
DealTask.all.map{|deal_task| deal_task.attributes.merge!(total_attachments: deal_task.attachments.count)}
Breaking it down...
DealTask.all.map do |deal_task|
...
end
Is going to return an array. The array will contain the results of the do block.
deal_task.
attributes.
with_indifferent_access
Gives you the attributes of each deal_task in a hash that can be access with strings or symbols (thus, "indifferent_access").
deal_task.
attributes.
with_indifferent_access.
slice(:id, :name)
Keeps only the :id and :name of the deal_task hash.
merge!(total_attachments: deal_task.attachments.count)
Adds the attachments count to your hash with the key total_attachments.
Results should look something like:
[
{id: 1, name: 'name1', total_attachments: 12},
{id: 2, name: 'name2', total_attachments: 3}
]
I found the best solution for Parent child relationship count
counter_cache: true
because all above queries take too much time to load from database
so you all must prefer to use this
1-> Add one column in Parent table called DealTask
rails g migration AddAttachmentsCountToDealTask attachments_count:integer
2-> Open Migration add Edit it
class AddAttachmentCountToDealTask < ActiveRecord::Migration[5.0]
def up
add_column :deal_tasks, :attachments_count, :integer, default: 0
DealTask.reset_column_information
DealTask.find_each do |deal_task|
DealTask.reset_counters deal_task.id, :attachments
end
end
def down
remove_column :deal_tasks, attachments_count
end
end
So when you rollback the migration it will not raise an error or exception
you can also use any loop instead of using
find_each, DealTask.all.each do...end
but yes, While resetting counter Must use class name like
DealTask.reset_counters
3-> Set Counter cache
class Attachment < ActiveRecord::Base
belongs_to :deal_task, foreign_key: :relation_id, counter_cache: true
end
class DealTask < ActiveRecord::Base
has_many :attachments, foreign_key: :relation_id
end
suppose name of your model is sub_tasks than your counter_cache column must be
sub_tasks_count
if you want your own column name than you have to specify that column name in counter_cache
suppose column name is total_subtasks than
belongs_to :deal_task, foreign_key: :relation_id, counter_cache: :total_subtasks
and make changes accordingly for updating counter_cache
now when you Add any Attachment, attachments_count column increase by 1 and this is done automatically by **counter_cache
one Problem is there
** when you delete any child counter_cache is unable to decrease **
so for that solution make a callback
class Attachment < ActiveRecord::Base
belongs_to :deal_task, foreign_key: :relation_id, counter_cache: true
before_destroy :reset_counter
private
def reset_counter
DealTask.reset_counters(self.relation.id, :attachments)
end
end
so when you delete any attachments it will reset countet_cache for its Parent by relation_id which is parent_id or Foreign_key for attachments
for more info
see video on Railscast counter cache 23
Try this
DealTask.all.map { |deal_task| deal_task.attachments.ids }.count
DealTask.first.attachments.count #This will give count of attachemenets
#To show all records and all the count
DealTask.find_each do |dt|
print dt.inspect
print "\n"
print dt.attachments.count
end
Or
DealTask.joins(:attachments).select("deal_tasks.*, count(attachements.id) as count").group("deal_tasks.id")
For much nicer format
DealTask.joins(:attachments)
.select("deal_tasks.id, deal_tasks.name, count(attachements.id) as attachments")
.group("deal_tasks.id")
.collect(&:attributes)
#This will gve you something like
[
{"id"=>34332630, "name"=>"some name", "attachments"=>1},
{"id"=>71649461, "name"=>"some name", "attachments"=>1}
]
This will be lot faster as you get all data in a single query

Nested include with where statements using ActiveRecord

the structure of my app is as follows:
c
lass Project < ActiveRecord::Base
has_and_belongs_to_many :team_members
has_one :legal_contract
has_many :documents
end
class ProjectsTeamMember < ActiveRecord::Base
belongs_to :project
belongs_to :team_member
end
class LegalContract < ActiveRecord::Base
belongs_to :project
end
class TeamMember < ActiveRecord::Base
has_many :projects_team_members
has_many :projects, through: :projects_team_members
has_and_belongs_to_many :projects
end
I am trying to find all team_members who are associated with legal_contract with one of ids from an array. Trying the includes method:
team_members = TeamMember.includes( {projects_team_members: [ { project: :legal_contract} ] } )
I wonder now how do I narrow it down now to pull only those tmembers and not all. I tried
arry= [1,3,5]
team_members = TeamMember.includes( {projects_team_members: [ { project: :legal_contract} ] }).where('legal_contract.id IN', arry)
but getting an error
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: ...s"."updated_at" AS t0_r6, "projects_team_members"."" AS t1_r...
Does anyone know? Thanks
**UPDATE**
Thanks to Karlingen
TeamMember.joins(:projects => :legal_contract).where(legal_contracts: { id: [1,3,5] })
You need to accomplish this through a join like so:
TeamMember.joins(:projects => :legal_contract).where(legal_contracts: { id: [1,3,5] })
First you join the projects table and its legal contract. And then you search for all legal contracts that has the given ids.
Do note that the key in the Where clause is written in plural since it is the name of the table.
You need to set a primary key for projects_team_members, are you sure you have one?
It appears as though Rails is creating invalid syntax when trying to find a projects_team_members:
"projects_team_members".""

Rails association with multiple foreign keys

I want to be able to use two columns on one table to define a relationship. So using a task app as an example.
Attempt 1:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
So then Task.create(owner_id:1, assignee_id: 2)
This allows me to perform Task.first.owner which returns user one and Task.first.assignee which returns user two but User.first.task returns nothing. Which is because task doesn't belong to a user, they belong to owner and assignee. So,
Attempt 2:
class User < ActiveRecord::Base
has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end
class Task < ActiveRecord::Base
belongs_to :user
end
That just fails altogether as two foreign keys don't seem to be supported.
So what I want is to be able to say User.tasks and get both the users owned and assigned tasks.
Basically somehow build a relationship that would equal a query of Task.where(owner_id || assignee_id == 1)
Is that possible?
Update
I'm not looking to use finder_sql, but this issue's unaccepted answer looks to be close to what I want: Rails - Multiple Index Key Association
So this method would look like this,
Attempt 3:
class Task < ActiveRecord::Base
def self.by_person(person)
where("assignee_id => :person_id OR owner_id => :person_id", :person_id => person.id
end
end
class Person < ActiveRecord::Base
def tasks
Task.by_person(self)
end
end
Though I can get it to work in Rails 4, I keep getting the following error:
ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id
TL;DR
class User < ActiveRecord::Base
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
end
Remove has_many :tasks in User class.
Using has_many :tasks doesn't make sense at all as we do not have any column named user_id in table tasks.
What I did to solve the issue in my case is:
class User < ActiveRecord::Base
has_many :owned_tasks, class_name: "Task", foreign_key: "owner_id"
has_many :assigned_tasks, class_name: "Task", foreign_key: "assignee_id"
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User"
belongs_to :assignee, class_name: "User"
# Mentioning `foreign_keys` is not necessary in this class, since
# we've already mentioned `belongs_to :owner`, and Rails will anticipate
# foreign_keys automatically. Thanks to #jeffdill2 for mentioning this thing
# in the comment.
end
This way, you can call User.first.assigned_tasks as well as User.first.owned_tasks.
Now, you can define a method called tasks that returns the combination of assigned_tasks and owned_tasks.
That could be a good solution as far the readability goes, but from performance point of view, it wouldn't be that much good as now, in order to get the tasks, two queries will be issued instead of once, and then, the result of those two queries need to be joined as well.
So in order to get the tasks that belong to a user, we would define a custom tasks method in User class in the following way:
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
This way, it will fetch all the results in one single query, and we wouldn't have to merge or combine any results.
Extending upon #dre-hh's answer above, which I found no longer works as expected in Rails 5. It appears Rails 5 now includes a default where clause to the effect of WHERE tasks.user_id = ?, which fails as there is no user_id column in this scenario.
I've found it is still possible to get it working with a has_many association, you just need to unscope this additional where clause added by Rails.
class User < ApplicationRecord
has_many :tasks, ->(user) {
unscope(:where).where(owner: user).or(where(assignee: user)
}
end
Rails 5:
you need to unscope the default where clause
see #Dwight answer if you still want a has_many associaiton.
Though User.joins(:tasks) gives me
ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
As it is no longer possible you can use #Arslan Ali solution as well.
Rails 4:
class User < ActiveRecord::Base
has_many :tasks, ->(user){ where("tasks.owner_id = :user_id OR tasks.assignee_id = :user_id", user_id: user.id) }
end
Update1:
Regarding #JonathanSimmons comment
Having to pass the user object into the scope on the User model seems like a backwards approach
You don't have to pass the user model to this scope.
The current user instance is passed automatically to this lambda.
Call it like this:
user = User.find(9001)
user.tasks
Update2:
if possible could you expand this answer to explain what's happening? I'd like to understand it better so I can implement something similar. thanks
Calling has_many :tasks on ActiveRecord class will store a lambda function in some class variable and is just a fancy way to generate a tasks method on its object, which will call this lambda. The generated method would look similar to following pseudocode:
class User
def tasks
#define join query
query = self.class.joins('tasks ON ...')
#execute tasks_lambda on the query instance and pass self to the lambda
query.instance_exec(self, self.class.tasks_lambda)
end
end
I worked out a solution for this. I'm open to any pointers on how I can make this better.
class User < ActiveRecord::Base
def tasks
Task.by_person(self.id)
end
end
class Task < ActiveRecord::Base
scope :completed, -> { where(completed: true) }
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
def self.by_person(user_id)
where("owner_id = :person_id OR assignee_id = :person_id", person_id: user_id)
end
end
This basically overrides the has_many association but still returns the ActiveRecord::Relation object I was looking for.
So now I can do something like this:
User.first.tasks.completed and the result is all completed task owned or assigned to the first user.
Since Rails 5 you can also do that which is the ActiveRecord safer way:
def tasks
Task.where(owner: self).or(Task.where(assignee: self))
end
My answer to Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations is just for you!
As for your code,here are my modifications
class User < ActiveRecord::Base
has_many :tasks, ->(user) { unscope(where: :user_id).where("owner_id = ? OR assignee_id = ?", user.id, user.id) }, class_name: 'Task'
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
Warning:
If you are using RailsAdmin and need to create new record or edit existing record,please don't do what I've suggested.Because this hack will cause problem when you do something like this:
current_user.tasks.build(params)
The reason is that rails will try to use current_user.id to fill task.user_id,only to find that there is nothing like user_id.
So,consider my hack method as an way outside the box,but don't do that.
Better way is using polymorphic association:
task.rb
class Task < ActiveRecord::Base
belongs_to :taskable, polymorphic: true
end
assigned_task.rb
class AssignedTask < Task
end
owned_task.rb
class OwnedTask < Task
end
user.rb
class User < ActiveRecord::Base
has_many :assigned_tasks, as: :taskable, dependent: :destroy
has_many :owned_tasks, as: :taskable, dependent: :destroy
end
In result, we can use it so:
new_user = User.create(...)
AssignedTask.create(taskable: new_user, ...)
OwnedTask.create(taskable: new_user, ...)
pp user.assigned_tasks
pp user.owned_tasks

Composite Keys or Join Table

I'm having trouble trying to figure out the best way to associate/join my tables 'teams' and 'schedules'. My models are:
class Team < ActiveRecord::Base
attr_accessible :city, :conf, :div, :key, :name
self.primary_key = "key" #Abbreviations for strings 'CHI', 'TB', 'SEA' etc.
has_many :schedules, foreign_key: [:away_team, :home_team]
end
class Schedule < ActiveRecord::Base
attr_accessible :away_team, :date, :home_team, :season, :week, :team_key
self.primary_keys = :away_team, :home_team #Abbreviations for strings 'CHI', 'TB', 'SEA' etc.
belongs_to :team, primary_key: "key"
end
I installed the gem "composite_primary_keys", "~> 5.0.13".
In rails console when I assign a variable
>> team = Team.find("SEA")
SELECT "teams".* FROM "teams" WHERE "teams"."key" = ? LIMIT 1 [["key", "SEA"]]
=> #<Team key: "SEA", city: "Seattle", name: "Seahawks", conf: "NFC", div: "West">
it displays Seattle perfectly, but when I run:
>> team.schedules
SELECT "schedules".* FROM "schedules" WHERE ("schedules"."away_team" = 'SEA' AND "schedules"."home_team" IS NULL)
=> []
The 'schedule' table has data for 'SEA' in the home_team column.
Would a join table be a better solution? If so, how would you do it? Any help would be greatly appreciated!
First: don't use composit keys, if you don't have to (and you don't in this case).
Primary keys are for accessing individual items, not for constraints or business logic.
In your case, the primary index can't be used to select schedules by home_team.
Also it's a good idea to use Rails conventions as much as possible. It makes life easier.
Use id as primary key and for joining tables.
I see that your schedule belongs to two teams, and team have two kinds of schedules (home and away). So you models could look like:
class Team < ActiveRecord::Base
attr_accessible :city, :conf, :div, :key, :name
has_many :home_schedules, class_name: 'Schedule', foreign_key: :home_team_id
has_many :away_schedules, class_name: 'Schedule', foreign_key: :away_team_id
def schedules
home_schedules + away_schedules
end
end
class Schedule < ActiveRecord::Base
attr_accessible :away_team, :date, :home_team, :season, :week, :team_key
belongs_to :home_team, class_name: 'Team'
belongs_to :away_team, class_name: 'Team'
end
then
team = Team.find_by_key('SEA')
team.schedules
Added
I would generate the model with
rails g model Team city conf div short_cut name
rails g model Schedule day:date season week:integer home_team_id:integer away_team_id:integer
You link teams to fans by adding team_id to Fan. You can always use the team's short_cut (as I named your former key attribute) to display or select the team, but let Rails use id for all the internal stuff.

How can I populate a join (many-to-many) table inside seeds.rb for ruby on rails?

I currently have a model for team.rb and user.rb, which is a many to many relationship. I have created the join table teams_users but I am not sure how to populate this table in my seeds.rb?
For example, I have :
user = User.create({ first_name: 'Kamil', last_name: 'Bo', email: 'bo#gmail.com'})
team = Team.create([{ name: 'Spot Forwards', num_of_games: 10, day_of_play: 4}])
But the following does not work???
TeamsUsers.create({ team_id: team.id, user_id: user.id })
I get a message :
uninitialized constant TeamsUsers
This isn't optimized but
user.team_ids = user.team_ids < team.id
user.save
or if this is the first team
user.team_ids = [team.id]
user.save
ALso start using has_many :through. then you will have a TeamUser model. it's a life saver if the join table needs more attributes
Pick a side to work from and then, as #drhenner suggests, use the _ids property to create the association. For example, working with the User model, create the teams first, then the users, assigning them to teams as you go:
teams = Team.create([
{ name: 'Team 1' },
{ name: 'Team 2' },
{ name: 'Team 3' },
# etc.
])
User.create([
{ name: 'User 1', team_ids: [teams[0].id, teams[2].id] },
{ name: 'User 2', team_ids: [teams[1].id, teams[2].id] },
{ name: 'User 3', team_ids: [teams[0].id, teams[1].id] },
# etc.
])
From comment above:
You can have multiple relationships configured on a has_many :through relationship. It's up to you which ones you want to implement. These are all the possibilities:
class Team < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :team
belongs_to :user
end
class User < ApplicationRecord
has_many :memberships
has_many :teams, through: :memberships
end
So, when dealing with the Team model, you can use: team.memberships and team.users;
when dealing with the User model, you can use: user.memberships and user.teams;
and if dealing with the join model, you can use: membership.team and membership.user.
You can omit the relationship references to the join model if you don't use it—especially if you're treating the relationship between Team and User like a standard has_and_belongs_to_many relationship:
class Team < ApplicationRecord
has_many :users, through: :memberships
end
class User < ApplicationRecord
has_many :teams, through: :memberships
end
This gives you team.users and user.teams.
Using Rails, say you have tables foos and bars in a many-to-many relationship using table foos_bars, you can seed their associations like this:
bar1 = Bar.find(1)
bar2 = Bar.find(2)
foo1 = Foo.find(1) # for example
foo1.bars << bar1
foo1.bars << bar2
foo1.save
This will update the joins table foos_bars with associations <foo_id:1, bar_id:1>, <foo_id:1, bar_id:2>
Hope this helps.

Resources