How do I show grouped rows from model? - ruby-on-rails

I have a model:
class Row < ActiveRecord::Base
belongs_to :description
attr_accesible :id, :q_id, :quantity, description_id
end
class Description < ActiveRecord::Base
has_many :rows
translate :description #globalize3, ignore it.
attr_accesible :id, :other
end
In this case, Description and Row have a 1:N relationship (N from 1 to 3).
What is the best way to show this in table format in Haml? For example if we have in the DB:
Row:
1 | 1 | 20 | 1
2 | 2 | 22 | 1
3 | 1 | 30 | 2
4 | 3 | 31 | 2
5 | 2 | 32 | 2
Description:
1 | asd
2 | zxc
I would like to show this form:
desc | q1 | q2 | q3 |
-----+----+----+----+
asd | 20 | 22 | -- |
zxc | 30 | 32 | 31 |
UPDATE
I added a new attribute (q_id) that indicates the "q" position (1, 2, or 3) to my Row model.

In your controller code:
#descriptions = Description.eager_load(:rows).order('descriptions.id, rows.q_id')
In your view:
%table
%tr
%th= 'desc'
%th= 'q1'
%th= 'q2'
%th= 'q3'
- #descriptions.each do |description|
%tr
%td= description.description
- (0..2).each do |i|
%td= description.rows[i].try(:quantity) || '--'
UPDATE
If you want to limit the rows to a specific user association, you can do so like this:
#descriptions = Description.eager_load(:rows).
where(rows: {user_id: some_user.id}).
order('descriptions.id, rows.q_id')
If you are concerned that this may exclude a description that has no matching rows, you can substitute a SQL fragment with a NULL alternative:
#descriptions = Description.eager_load(:rows).
where('rows.user_id = ? OR rows.id IS NULL', some_user.id).
order('descriptions.id, rows.q_id')

Something like:
%table
%tr
- Description.includes(:rows).find_each do |description|
%td= description.description
%td= format_quantity description.rows.first
%td= format_quantity description.rows.second
%td= format_quantity description.rows.third
And a helper function
def format_quantity(row)
row.present? ? row.quantity : '--'
end
UPDATE
For the order of rows, you could specify that on your relation
class Description < ActiveRecord::Base
has_many :rows, order: 'q_id'

Related

Why is my mnesia index in this position?

iex(6)> :mnesia.table_info(:users, :attributes)
[:id, :email, :foo, :inserted_at, :updated_at]
iex(7)> :mnesia.table_info(:users, :index)
[3]
iex(8)> :mnesia.add_table_index(:users, :email)
{:aborted, {:already_exists, :users, 3}}
I get that Tab is the first index, but then why isn't the index on 2 instead of 3? Are the indexes 1 based instead of zero, or is something else at play here?
Mnesia tables contain tuples that have a record-like format. In your example the tuple that is stored in the :users table looks something like:
{:users, 1, "f#b.com", "foo", {2016, 12, 24}, {2016, 12, 31}}
Which maps to:
Index | 1 | 2 | 3 | 4 | 5 | 6 |
Name | :users | :id | :email | :foo | :inserted_at | :updated_at |
Since tuples are 1-based an index created for the :email value will be created on position 3 of the tuple.

Rendering a JSON object of a join-model and its associated models

In a Rails ( 4.1.5 / ruby 2.0.0p481 / win64 ) application I have a many-to-many relationship between Student and Course and a join model StudentCourse which represents the association, and has an additional attribute called started (set by default on "false").
I also have added an index in the join-table made of student_id and course_id, and set a unique check on that, like this
t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'
I wanted that to be a composite primary key, but since in rails there are no composite primary keys (without using a gem) I also added a primary key called id:
t.column :id, :primary_key
Now I see that associations are created by either doing:
Student.first.courses.create(:name => "english")
or
Course.first.students << Student.first
This is fine and it's the expected behaviour, I suppose.
That said, I am struggling to wrap my mind around association resolutions in ActiveRecord queries. Let me explain this better:
For reference, Student.all, Course.all and StudentCourses.all would return tables like these:
Student.all
+----+-----------+
| id | name |
+----+-----------+
| 1 | Aidan |
| 2 | Alison |
| 3 | Elizabeth |
+----+-----------+
Course.all
+----+----------+------------------+
| id | name | description |
+----+----------+------------------+
| 1 | english | desc. here |
| 2 | music | desc. here |
| 3 | dance | desc. here |
| 4 | science | desc. here |
| 5 | french | desc. here |
| 6 | arts | desc. here |
+----+----------+------------------+
StudentCourse.all
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 1 | 1 | false | 1 |
| 2 | 1 | false | 2 |
| 3 | 1 | false | 3 |
| 1 | 2 | true | 4 |
| 2 | 2 | true | 5 |
| 4 | 3 | false | 6 |
| 5 | 2 | false | 7 |
| 5 | 1 | true | 8 |
| 6 | 2 | false | 9 |
+-------------+------------+------------+----+
So far I can happily render a json object of all courses, and names of all students for each course like this:
render json: Course.all.to_json(:include => {:students => {:only => :name}})
I can also easily render all courses that a student is attending or about to attend with
render json: #student.courses.to_json(:include => {:students => {:only => :name}})
which also includes other students for those courses.
But suppose I wanted to render one student's courses which the student has not yet started, together with all the other students who are on that course (which have or not started the course) [Please read the UPDATE section below, I'm looking for the opposite thing actually!]
I think the easieast approach is to query the join-table for something like:
StudentCourse.all.where(student_id: #student.id, started: false)
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 5 | 2 | false | 7 |
| 6 | 2 | false | 9 |
+-------------+------------+------------+----+
But how do I go on from this resulting table (association object) to get a nicely packaged json object with courses names (and all other attributes) and also including students in it, like I did with: Course.all.to_json(:include => {:students => {:only => :name}}) ?
I think I'm missing some basic knowledge of some important key concepts here, but at this point I cannot even indentify them and would greatly appreciate some help... thank you.
Update:
I just realized that the following part is what I was originally trying to do. It's the opposite thing. Among all these details I got lost along the path. I hope that it's ok if I just add it here.
So, given a student (let's call him Aiden), I need to return a JSON object containing only the courses that he is in and that he has started, only when such courses have other students in them who have not started them, and it has to include the names of those students for each course too.
So...
I now have:
aiden_started_courses = Student(1).courses.where(:student_courses => {:started => true } )
which for a student takes all the courses that have a "true" value in the join-table "started" column. (again in the join table each student-course record is "compositely" unique, so there can just be one unique record for a given student_id and course_id).
With the next query, for one of "aiden_started_courses" I can pull off all the relative student-courses associations which have a false value on "started"
aiden_started_courses[0].student_courses.where(:started => false).includes(:student).to_json(:include => :student)
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 1 | 2 | false | 4 |
+-------------+------------+------------+----+
| 1 | 9 | false | 5 |
+-------------+------------+------------+----+
So here lies the problem: I have managed to get this just for a single course in aiden_started_courses array, but how would I be able to build a query that returns this data for all of Aiden's started courses?
Is it possible to do that in one line? I know I could probably use Ruby enumerator loops but I somewhat feel that I would be kind of breaking some pattern both on a Rails coding convention level and on performance level? (hitting N+1 problem again?) ...
What I could so far:
I came up with this where I find all students who have not started the courses which a given user has started:
Student.includes(:student_courses).
where(:student_courses => { :started => false, :course_id => aiden.courses.where
(:student_courses => {started: true}).ids } )
or this:
Course.includes(:students).where(:student_courses => {:started => false,
:course_id => aiden.courses.where(:student_courses => {:started =>true}).ids })
which finds all the courses that a given student has started if those courses include students who have not started them yet
But what I really need is to get a JSON object like this:
[
{
"id": 1,
"name": "english",
"students": [
{"name": "ALison"},
{"name": "Robert"}]
},
{
"id": 2,
"name": "music",
"description": null,
"students": [
{"name": "Robert"},
{"name": "Kate"}]
}
]
where I can see the courses that a given student is on and has started, but only those in which there are other students that have not yet started it, together with the names of those students...
I'm thinking that probably there is no way how I could get that through a regular AR query, so maybe should a build a JSON manually? But how could I do that?
Thanks in adv. and I apologise for the verbosity.. but hopefully it will help..
Use scope to your advantage:
class Course < ActiveRecord::Base
# ...
scope :not_started, -> { joins(:student_courses) \
.where(student_courses: {started: false}) }
scope :with_classmates, -> { includes(:students) } # use eager loading
end
Then call:
#student.courses.not_started.with_classmates \
.to_json(include: {students: {only: :name}})
Output:
[
{
"id": 1,
"name": "english",
"description": null,
"students": [
{"name": "Aiden"},
{"name": "Alison"}]},
{
"id": 2,
"name": "music",
"description": null,
"students": [
{"name": "Aiden"},
{"name": "Alison"}]},
{
"id": 3,
"name": "dance",
"description": null,
"students": [
{"name": "Aiden"}]}]
Use JBuilder, it comes by default with Rails. Ignore the lines starting with '#':
Jbuilder.new do |j|
# courses: [{
j.courses <student.courses - replace this with whatever variable> do |course|
# id: <course.id>
j.id course.id
# name: <course.name>
j.name course.name
# students: [{
j.students <course.students - replace with whatever variable> do |student|
# name: <student.name>
j.name student.name
end
# }]
end
# }]
end
Not a lot of code. Removing the comments and simplifying some features, it will look like:
student_courses = <...blah...>
json = Jbuilder.new do |j|
j.courses student_courses do |course|
j.(course, :id, :name)
j.students <course.students - whatever method here>, :name
end
end.target!
Check out their guide, its pretty awesome to generate JSON in plain Ruby DSL. So go ahead and use whatever ruby code you want to fetch students/courses/studentcourses.

AwesomeNestedSet and sortable tree

In default AwesomeNestedSet gem is sorting by :lft attribute. Suppose I have a class:
class Category < ActiveRecord::Base
acts_as_nested_set
attr_accessible :name, :position, :parent_id, :lft, :rgt
end
How can I create a sortable (by :position attribute) tree with AwesomeNestedSet gem with one hit to the database where :position is used for sorting siblings (level)?
I need output something like this:
----------------------------------
id |position | name | parent_id |
----------------------------------
1 | 1 | item1 | nil |
----------------------------------
2 | 1 | item11 | 1 |
----------------------------------
3 | 1 | item111| 2 |
----------------------------------
4 | 2 | item12 | 1 |
----------------------------------
5 | 2 | item2 | nil |
----------------------------------

Can't show information from another model

I did an application in rails and I'm trying to use a column type_id in relationship with my other table
My tables:
sinisters
+---+----------+---------+
|id | client | type_id |
+---+----------+---------+
| 1 | Charles | 1 |
| 2 | Montalvo | 1 |
| 3 | Gabriel | 2 |
| 4 | Miguel | 2 |
+---+----------+---------+
sinister_types
+----+--------+
| id | name |
+----+--------+
| 1 | DANGER |
| 2 | FIRE |
+----+--------+
My controller:
#sinisters = Sinister.find(:all)
My models:
class Sinister < ActiveRecord::Base
belongs_to :sinister_type
end
class SinisterType < ActiveRecord::Base
has_many :sinisters
end
My view:
<% #sinisters.each |sinister| do %>
<%= sinister.client %>
<%= sinister.type.name %>
<% end %>
I want to show sinister types names.
I tried this code and got nothing:
<%= sinister.sinister_type.name %>
And also tried and got nothing
<%= sinister.type.try(:name) %>
Please somebody can help me?
Like #backpackerhh says, the default convention is sinister_type_id, not type_id. But if you want to override it, you need to specify :foreign_key.
Model :
class Sinister < ActiveRecord::Base
belongs_to :sinister_type, :foreign_key => :type_id
end
class SinisterType < ActiveRecord::Base
has_many :sinisters
end
Controller :
#sinisters = Sinister.find(:all)
View :
Not #sinisters.each |sinister| do, but #sinisters.each do |sinister|
<% #sinisters.each do |sinister| %>
<%= sinister.client %> :
<%= sinister.sinister_type.name %>
<% end %>
I think your column on sinister table should be sinister_type_id (instead of type_id) or you need to specify the foreign key on your model.

RoR: Check boxes in form_for for has_many, :through with a Join model

Once again, I have my form_for for my Order model, which has_many :services, :through => :requests. Here's the layout of how I have my relationships mapped:
__________ _________
| Customer | | Utility |
---------- ---------
|| ^ /\
|| | ||
\/ | /\
_______ _________ _________
| Order | <=====< | Request | >=====> | Service |
------- --------- ---------
\/
||
\/
_________
| Company |
---------
Where:
---> = belongs_to
===> = has_many
<==< join model >==> = has_many, :through
On my Order form, I want to have an array of checkboxes that represent the services available, such that even though the checkboxes are labelled by the Company and categorized by Utility, the Order ends up with the Service association when the order is complete (because that's really what the customer is ordering: a Company to provide a Utility, which is a Service).
How do I accomplish this in my form?
form view:
- form_for #order do |order_form|
-# order form inputs, etc.
- order_form.fields_for :customer do |customer_form|
-# customer form inputs
- order_form.fields_for :services do |services_form|
%dl
- #services.each do |service_name, services|
%dt= service_name
- services.each do |service_item|
%dd
=# check_box ??????????
=# label ??????????, Company.find(service_item.company_id).name
%p= order_form.submit 'Create Order'
Where:
#services = Service.all.to_set.classify { |service_item| Utility.find(service_item.utility_id).name }
There is a Railscast on HABTM checkboxes -- it's an oldie but goodie. I'm pretty sure it should still work even with a join model.

Resources