Rails not ordering by date for this query - ruby-on-rails

I'm currently working on a website where a parent can reserve classes for their kids. In the parent's dashboard, I'd like to show a table of schedules and in every row there will be the child's name associated with a schedule. Unfortunately I have problem ordering the schedules by date in the parent's dashboard.
# view
<% #reservations.each do |reservation| %>
<%= reservation.child.first_name %>
<%= reservation.schedule.date %>
<%= reservation.schedule.start_time %> - <%= reservation.schedule.end_time %>
<%= link_to reservation.schedule.klass.name, schedule_path(reservation.schedule) %></td>
<%= link_to reservation.schedule.partner.company, partner_path(reservation.schedule.partner) %></td>
<%= reservation.schedule.city.name %></td>
<% end %>
# associations
User
has_many :children, dependent: :destroy
has_many :reservations
has_many :schedules, through: :reservations
Child
belongs_to :user
has_many :reservations, dependent: :destroy
has_many :schedules, through: :reservations
Reservation
belongs_to :child
belongs_to :schedule
belongs_to :user
Schedule
belongs_to :city
belongs_to :partner
belongs_to :activity
belongs_to :klass
has_many :reservations
has_many :children, through: :reservations
I've got a default scope in my Schedule model that orders by date, start_time and end_time.
# Schedule model
default_scope { order(:date, :start_time, :end_time) }
This scope works in other tables, but not for this query:
# controller
#reservations = current_user.reservations.includes(:child, schedule: [:partner, :klass, :city])
It just refuses to order by date and time in the browser:
The log shows that the query for Schedule is indeed being ordered:
Reservation Load (0.3ms) SELECT "reservations".* FROM "reservations" WHERE "reservations"."user_id" = $1 [["user_id", 1]]
Child Load (0.4ms) SELECT "children".* FROM "children" WHERE "children"."id" IN (1, 2, 3)
Schedule Load (0.4ms) SELECT "schedules".* FROM "schedules" WHERE "schedules"."id" IN (24, 12) ORDER BY "schedules"."date" ASC, "schedules"."start_time" ASC, "schedules"."end_time" ASC
Partner Load (0.3ms) SELECT "partners".* FROM "partners" WHERE "partners"."id" IN (2)
Klass Load (0.3ms) SELECT "klasses".* FROM "klasses" WHERE "klasses"."id" IN (9, 17)
City Load (0.4ms) SELECT "cities".* FROM "cities" WHERE "cities"."id" IN (28)
I could do this query in the controller instead:
#schedules = current_user.schedules
but then I'd have problem showing only a single child's name for each schedule since a class schedule can have many children associated for it.
Help?

You've defined an ordering on the default scope for Schedule, but you are pulling your relation from Reservation. As a result, the schedules are queried in the order specified, but since presumably you're looping through the #reservations in your view, you see their order and not their schedules's order. You can order #reservations by fields on the schedules table like so:
#reservations = #reservations.order("schedules.date ASC, schedules.start_time ASC, schedules.end_time ASC")
Also note that defining a default_scope in the model is generally discouraged (on SO and elsewhere) because its difficult to manage and can cause unexpected and unintuitive effects, especially down the road.

Related

Querying a join table?

I am trying to do two things:
query an attribute from an inner join table in Rails' Console.
query and displaying the attribute in a view.
These are my Models:
retreat.rb:
class Retreat < ApplicationRecord
belongs_to :user
belongs_to :account
validates :name, presence: true
has_many :retreats_teams
has_many :teams, through: :retreats_teams
accepts_nested_attributes_for :retreats_teams
end
retreats_team.rb:
class RetreatsTeam < ApplicationRecord
belongs_to :team
belongs_to :retreat
end
team.rb:
class Team < ApplicationRecord
belongs_to :account
has_many :team_members
has_many :users, through: :team_members
accepts_nested_attributes_for :team_members
has_many :retreats
has_many :retreats, through: :retreats_teams
end
In Rails' console, if I type:
Retreat.last.teams
I get the output:
irb(main):008:0> Retreat.last.teams
Retreat Load (0.9ms) SELECT "retreats".* FROM "retreats" ORDER BY "retreats"."id" DESC LIMIT $1 [["LIMIT", 1]]
Team Load (0.9ms) SELECT "teams".* FROM "teams" INNER JOIN "retreats_teams" ON "teams"."id" = "retreats_teams"."team_id" WHERE "retreats_teams"."retreat_id" = $1 LIMIT $2 [["retreat_id", 38], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Team id: 56, name: "My house", account_id: 2, created_at: "2020-02-10 15:57:25", updated_at: "2020-02-10 15:57:25">]>
irb(main):009:0>
How do I retrieve the team name: "My house"?
Also, there might be many teams that display here, too.
#teams returns a collection of team objects. The simplest solution is to call first on the teams to get the first team in the collection:
Retreat.last.teams.first.name
=> "My house"
But if you want all the names in teams you might use pluck. This will allow you to do this:
retreat = Retreat.last
foo = Team.create(name: 'Foo')
bar = Team.create(name: 'Bar')
retreat.teams << foo
retreat.teams << bar
retreat.teams.pluck(:name).to_sentence
=> "My house, Foo, and Bar"
A word on naming
The naming convention for join models is SingularSingular. The table should be named singular_plural. has_and_belongs_to_many is the only part of Rails that actually uses the oddball plural_plural naming scheme.
RetreatsTeam # bad
RetreatTeam # better
Even better though is to actually give your join tables meaningful names instead of just placeholder names.
1) querying an attribute from an inner join table in Rails Console.
Since the association between Retreat and RetreatsTeams in one to many you can actually only fetch aggregates. Otherwise which attribute should it fetch, from the first row, the last row or all the rows?
So for example you can do:
Retreat.joins(:retreats_teams)
.select('retreats.*', 'COUNT(retreats_teams.*) AS retreats_teams_count')
If you are storing more data on the join table that you want to display you want to iterate through the join table:
#retreat = Retreat.eager_load(retreats_teams: :teams).first
#retreat.retreats_teams.each do |rt|
puts rt.foo
puts rt.team.name
end
2) querying and displaying the attribute in a view.
In Rails you're usually just fetching records in the controller and then iterating through them in the view:
class ResortsController < ApplicationController
def show
#resort = Resort.includes(:teams).find(params[:id])
end
end
# app/views/resorts/show.html.erb
<h1><%= #resort.name %></h1>
<h2>Teams</h2>
<% if #resort.teams.any? %>
<ul>
<% #resort.teams.each do |team| %>
<li><%= team.name %></li>
<% end %>
</ul>
<% else %>
<p>This resort has no teams</p>
<% end %>

RoR: fetch has_many association through another has_many

I'm following official documentation: http://guides.rubyonrails.org/association_basics.html at section 4.3.3.4
I have following models:
class Nomination < ActiveRecord::Base
belongs_to :festival
has_many :festival_histories, -> { includes :awards }
attr_accessible :name
end
class FestivalHistory < ActiveRecord::Base
has_many :awards
belongs_to :nomination
belongs_to :festival
end
class Award < ActiveRecord::Base
belongs_to :festival_history
belongs_to :case, inverse_of: :awards
has_attached_file :image
attr_accessible :name, :festival_history_id, :image
end
Which looks very similar (for me) to example in documentation.
But when I do in console:
n = Nomination.first
n.festival_histories.awards
I get
NoMethodError: undefined method `awards' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_FestivalHistory:0x000001019cd400>
I've reloaded console, so issue is not there for sure...
There is no problem with documentation:)
As JTG said, you couldn't get awards on all festival_histories, only on specific history.
The difference is:
With include option:
n = Nomination.first
Nomination Load (0.4ms) SELECT "nominations".* FROM "nominations" ORDER BY "nominations"."id" ASC LIMIT 1
n.festival_histories
FestivalHistory Load (25.5ms) SELECT "festival_histories".* FROM "festival_histories" WHERE "festival_histories"."nomination_id" = ? [["nomination_id", 1]]
Award Load (0.7ms) SELECT "awards".* FROM "awards" WHERE "awards"."festival_history_id" IN (1)
n.festival_histories.first.awards
NO QUERY!
Without include option:
n = Nomination.first
Nomination Load (0.4ms) SELECT "nominations".* FROM "nominations" ORDER BY "nominations"."id" ASC LIMIT 1
n.festival_histories
FestivalHistory Load (25.5ms) SELECT "festival_histories".* FROM "festival_histories" WHERE "festival_histories"."nomination_id" = ? [["nomination_id", 1]]
n.festival_histories.first.awards
Award Load (0.7ms) SELECT "awards".* FROM "awards" WHERE "awards"."festival_history_id" = ? [["festival_history_id", 1]]
I think difference is obvious now:)
class Nomination < ActiveRecord::Base
belongs_to :festival
has_many :festival_histories, -> { includes :awards }
has_many :awards, through: :festival_histories
attr_accessible :name
end
Then you can call
Nomination.first.awards
Here's what's going wrong in you console, since festival_histories is an a collection of records, you cannot get the awards for a collection, only an individual record. So instead of
n = Nomination.first
n.festival_histories.awards
You need
n = Nomination.first
n.festival_histories.each { |r| puts r.awards}
to see the awards for each festival_history.
(So yes, how you are include: the :awards for lazy loading is working, and it's not a mistake in the documentation ;))

Create join model record with accepts_nested_attributes_for

I have following models
class User < ActiveRecord::Base
has_many :project_users, dependent: :destroy
has_many :projects, through: :project_users
end
class ProjectUser < ActiveRecord::Base
belongs_to :user
belongs_to :project
has_many :participants
has_many :tasks, through: :participants
end
class Task < ActiveRecord::Base
belongs_to :project
has_many :participants
has_many :project_users, through: :participants
accepts_nested_attributes_for :participants
end
class Participant < ActiveRecord::Base
belongs_to :project_user
belongs_to :task
end
So the flow should go like this:
1. User creates the project
2. User adds users to project via the join model ProjectUser
3. User creates a task for the project and selects those users from ProjectUsers, who will participate in the task. So I put an accepts_nested_attributes_for method and tried to build the nested form.
In my controller:
def new
#task = Task.new
#task.participants.build
end
def task_params
params.require(:task).permit(:project_id, :project_phase_id, :priority_id, :title, :due_date, :estimation, :responsible_id, :description, :participant_ids => [])#, :participants_attributes => [:project_user_id, :task_id])
end
participants_attributes is commented
In my view:
= f.association :participants, as: :select
Actual HTML generated:
<input name="task[participant_ids][]" type="hidden" value="">
<select class="select optional form-control" id="task_participant_ids" multiple="multiple" name="task[participant_ids][]">
<option value="57">AlexandeR MazeiN</option>
<option value="59">Firenze</option>
<option value="58">Vasily Strekotkin</option>
</select>
I add options via ajax, value = ProjectUser.id
I Have to do it like so, because I dont know which ProjectUsers there will be unless a concrete project for a task is selected.
Error I am getting:
Started POST "/tasks" for 127.0.0.1 at 2014-04-11 13:18:24 +0300
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
Processing by TasksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"aXuk9ZuDvFZce+sbIQLRhWZlVjitMvySaJ7CwWfdmaQ=", "task"=>{"project_id"=>"20", "priority_id"=>"4", "project_phase_id"=>"40", "title"=>"Skepta", "due_date"=>"", "estimation"=>"8", "responsible_id"=>"6", "participant_ids"=>["", "57", "58"], "description"=>""}, "commit"=>"Create Task"}
Team Load (0.4ms) SELECT "teams".* FROM "teams" WHERE "teams"."id" = $1 LIMIT 1 [["id", 3]]
Participant Load (0.5ms) SELECT "participants".* FROM "participants" WHERE "participants"."id" IN (57, 58)
Completed 404 Not Found in 7ms
ActiveRecord::RecordNotFound - Couldn't find all Participants with IDs (57, 58) (found 0 results, but was looking for 2):
Your param hash has the IDs from ProjectUser as participant_ids, so when it queries the database it is looking for Participant models with these IDs. You need to set these as project_user_id inside of a list of participants, something like this:
participants: [ { project_user_id: 57 }, { project_user_id: 58 } ]
I'm not super familiar with build, but something along these lines should allow AR to properly construct the associations.

Rails 3 - dependent => :delete_all doesn't work. Why?

I have few models in my project. There are some of them: Qualification and Curriculum. Qualification has children (curriculums). I want to make sure that when i delete Qualification i delete all it's children. Here is me code:
# Table name: qualifications
#
# id :integer not null, primary key
# subject_id :integer
# teacher_id :integer
# created_at :datetime not null
# updated_at :datetime not null
class Qualification < ActiveRecord::Base
belongs_to :subject
belongs_to :teacher
has_many :curriculums, :dependent => :delete_all
has_many :school_classes, :through => :curriculums
end
# id :integer not null, primary key
# school_class_id :integer
# qualification_id :integer
# created_at :datetime not null
# updated_at :datetime not null
class Curriculum < ActiveRecord::Base
belongs_to :school_class
belongs_to :qualification
has_one :result
has_many :timetables
end
As you can see i tried to use :dependent => :delete_all in Qualification model. But it doesn't work. Why?
UPD:
I deleting qualification by unchecking checkboxes in view when i EDIT it:
<div class="control-group">
<%= f.label :subject_ids, "Teacher can teach such subjects in the school",
:class => "control-label" %>
<div class="controls">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Choose</th>
<th>Subject</th>
</tr>
</thead>
<tbody>
<%= hidden_field_tag "teacher[subject_ids][]", nil %> <%# We use hidden field because it doesn't submit unchecked fields. So, we pass nil and nothing will be submitted.%>
<% #subjects.each do |subject| %>
<tr>
<td>
<%= check_box_tag "teacher[subject_ids][]", # [] brackets tells that this is array.
subject.id, # Value of checkbox.
#teacher.subject_ids.include?(subject.id), # Here we automatically check checkboxes.
id: dom_id( subject ) %> <%# Give unique id for each value. 'dom_id' is Rails helper. We will have ids like: 'subject_1', 'subject_2' and etc. %>
</td>
<td>
<%= label_tag dom_id( subject ), subject.subject_name %> <%# Put name of subject. %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
Here is more info:
class Teacher < ActiveRecord::Base
has_many :qualifications
has_many :subjects, :through => :qualifications
end
class Subject < ActiveRecord::Base
has_many :qualifications
has_many :teachers, :through => :qualifications
end
Here is SQL code when i update my model:
Started PUT "/teachers/2" for 127.0.0.1 at 2012-06-03 18:34:44 +0400
Processing by TeachersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ZJNNV9/TO6k18O1Ar1kpkU+PWbd7btHm9Tc067iMNO4=", "teacher"=>{"teacher_last_name"=>"Last", "teacher_first_name"=>"First", "teacher_middle_name"=>"Middle", "teacher_sex"=>"m", "teacher_birthday"=>"1980-12-01", "teacher_phone_attributes"=>{"teacher_mobile_number"=>"88283686", "teacher_home_number"=>"5112787", "id"=>"2"}, "teacher_education_attributes"=>{"teacher_education_university"=>"Mmm", "teacher_education_year"=>"1970-01-01", "teacher_education_graduation"=>"Graduated", "teacher_education_speciality"=>"Math", "id"=>"2"}, "teacher_category"=>"1st", "subject_ids"=>["", "4", "3", "1"]}, "commit"=>"Update", "id"=>"2"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
Teacher Load (0.4ms) SELECT "teachers".* FROM "teachers" WHERE "teachers"."id" = $1 LIMIT 1 [["id", "2"]]
(0.1ms) BEGIN
Subject Load (0.5ms) SELECT "subjects".* FROM "subjects" WHERE "subjects"."id" IN (4, 3, 1)
Subject Load (0.5ms) SELECT "subjects".* FROM "subjects" INNER JOIN "qualifications" ON "subjects"."id" = "qualifications"."subject_id" WHERE "qualifications"."teacher_id" = 2
SQL (0.4ms) DELETE FROM "qualifications" WHERE "qualifications"."teacher_id" = 2 AND "qualifications"."subject_id" = 2
TeacherPhone Load (0.4ms) SELECT "teacher_phones".* FROM "teacher_phones" WHERE "teacher_phones"."teacher_id" = 2 LIMIT 1
TeacherEducation Load (0.4ms) SELECT "teacher_educations".* FROM "teacher_educations" WHERE "teacher_educations"."teacher_id" = 2 LIMIT 1
(24.1ms) COMMIT
Redirected to http://0.0.0.0:3000/teachers
Completed 302 Found in 57ms (ActiveRecord: 27.2ms)
Try:
:dependent => :destroy
instead of :dependent => :delete_all
Not sure if this is what you are doing but I'm able to delete dependents using :dependent => :delete_all
Instead of using delete, I used destroy in my controller.
Example:
Instead of:
Qualification.delete(params[:id])
Use this:
Qualification.destroy(params[:id])
Hope this helps. =)
the "the rails 3 way" says that -
(pg 187)
clear
Transactionally removes all records from this association by clearing the foreign key
field (see delete). If the association is configured with the :dependent option set to
:delete_all, then it calls delete_all. Similarly, if the :dependent option is set to
:destroy_all, then the destroy_all method is invoked.
delete(*records) and delete—all
The delete and delete_all methods are used to sever specified associations, or all of
them, respectively. Both methods operate transactionally.
It’s worth noting, for performance reasons, that calling delete_all first loads the
entire collection of associated objects into memory in order to grab their ids. Then it
executes a SQL UPDATE that sets foreign keys for all currently associated objects to nil,
effectively disassociating them from their parent. Since it loads the entire association into memory, it would be ill-advised to use this method with an extremely large collection of associated objects.
Note
The names of the delete and delete_all methods can be misleading. By default, they
don’t delete anything from the database—they only sever associations by clearing the foreign
key field of the associated record. This behavior is related to the :dependent option, which
defaults to :nullify. If the association is configured with the :dependent option set to
:delete or :destroy, then the associated records will actually be deleted from the database.
:dependent => :destroy or :delete
Specifies a rule that the associated owner record should be destroyed or just deleted from
the database, depending on the value of the option. When triggered, :destroy will call
the dependent’s callbacks, whereas :delete will not.
Usage of this option might make sense in a has_one / belongs_to pairing. However,
it is really unlikely that you want this behavior on has_many / belongs_to relationship;
it just doesn’t seem to make sense to code things that way. Additionally, if the owner
record has its :dependent option set on the corresponding has_many association, then
destroying one associated record will have the ripple effect of destroying all of its siblings.

order products by association count belongs_to

Class Sale
belongs_to :product, :accessible => true
belongs_to :brand, :accessible => true
end
Class Product
has_many :sales
belongs_to :brand
end
Class Brands
has_many :products
has_many :sales
end
How do i get the brands that have the most product sales?
If you want to stay with activerecord you can use Calculations but it will take 2 queries to accomplish it:
>> brands = Sale.count(:id, :include => :brand, :group=> 'sales.brand_id', :limit => 5)
SQL (0.7ms) SELECT count(DISTINCT `sales`.id) AS count_id, sales.brand_id AS sales_brand_id FROM `sales` LEFT OUTER JOIN `brands` ON `brands`.id = `sales`.brand_id GROUP BY sales.brand_id LIMIT 5
=> #<OrderedHash {967032312=>3, 1392881137=>1}>
>> brands_with_most_sales = Brand.find(brands.keys)
Brand Load (0.5ms) SELECT * FROM `brands` WHERE (`brands`.`id` = 967032312)
=> [#<Brand id: 967032312, title: "brand-x", created_at: "2009-11-19 02:46:48", updated_at: "2009-11-19 02:46:48">]
Otherwise you might want to write you own query using find_by_SQL

Resources