How to display category and subcategory - ruby-on-rails

There is a table with fields: id, name, id_cat
Please show an example of a method that outputs these categories. And where you want to implement this method in the controller or in helpers? Help implement this recursive method.

As I correctly understand you have Category table with circular reference realised by column id_cat.
Following model should works as you expected:
class Category < ActiveRecord::Base
belongs_to :supercategory, :class_name => "Category", :foreign_key => "cat_id"
has_many :subcategories, :class_name => "Category", :foreign_key => "cat_id"
end
So if categories table looks like:
id | name | cat_id
---------------------------
1 | cat 1 | null
2 | cat 1.1 | 1
3 | cat 1.2 | 1
4 | cat 2 | null
5 | cat 1.2.1 | 3
Category.find(1).subcategories returns array with cat 1.1 and cat 1.2 objects.
Category.find(1).supercategory returns nil
Category.find(3).supercategory returns cat 1 object
Category.find(4).supercategory returns nil
...

Related

ActiveRecord: how to build has_many relation with foreign key or null

I have these two models:
class Promotion < ActiveRecord::Base
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :promotions
end
And consider these 3 promotions:
--------------------------
| PROMOTIONS |
--------------------------
| id | name | product_id |
| 1 | sale | NULL |
| 2 | 10% | 1 |
| 3 | 20% | 2 |
--------------------------
When the product_id is NULL, the promotion applies to all products, so I'd like to be able to get all the promotions for a product as follows:
Product.find(1).promotions # => [1,2]
Product.find(2).promotions # => [1,3]
How can I achieve this?
You could go about finding promotions like this a few different ways. One way would be to just access the Promotion model directly
promotions1 = Promotion.where(product_id: [1, nil])
promotions2 = Promotion.where(product_id: [2, nil])
You could add this as a method in your Product model
class Product < ActiveRecord::Base
def all_promotions
Promotion.where(product_id: [self.id, nil])
end
end
And then use it as follows:
Product.find(1).all_promotions # => [1,2]
Another way could be to concat the promotions of Product object with all promotions that aren't attached to a specific product. This definitely isn't the best solution, since you can't really take advantage of ActiveRecord to order the promotions; you'd have to use array sorting.
def all_promotions
self.promotions + Promotion.where(product_id: nil)
end

Counting entries on association table and inject it into a model, using Rails

The goal
Count entries on association table and inject it into a model, using Rails (v. 4.1).
The scenario
There is game.rb model:
class Game < ActiveRecord::Base
has_and_belongs_to_many :genres
end
and there is also a genre.rb model:
class Genre < ActiveRecord::Base
end
In the database, there are three tables: games, genres, games_genres – and games is still empty because I'm still developing genres area. So, genres' table is like following:
+----+-----------+
| id | name |
+----+-----------+
| 1 | Action |
| 2 | RPG |
+----+-----------+
And this is games_genres table:
+----+--- -----+----------+
| id | game_id | genre_id |
+----+---------+----------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
+----+---------+----------+
The problem
My application has an API and to retreive genres, I'm doing this way:
class API::V1::TargetsController < ApplicationController
def index
render json: Genre.all.to_json
end
end
The output is something like this:
[
{
id: 1,
name: 'Action'
},
{
id: 2,
name: 'RPG'
}
]
But, I want to inject the count of how many products has each genre. The query is simple:
SELECT COUNT(genre_id) AS products_quantity FROM game_genres
So, how can I inject products_quantity see above's query within Genre model? Something to get the JSON's output like this:
[
{
id: 1,
name: 'Action',
products_quantity: 2
},
{
id: 2,
name: 'RPG',
products_quantity: 1
}
]
You can add a method option to the to_json method. Thus you can define a product_quantity method on your Genre model
def product_quantity
game_genres.count
end
and then have it included in the to_json call.
class API::V1::TargetsController < ApplicationController
def index
render json: Genre.all.to_json(methods: :product_quantity)
end
end
While the above will work, I would suggest you use something more robust like rabl to handle JSON responses.

Ruby on Rails - Model Relations

Im working on a little app for Ruby on Rails and Ive got a question regarding the relationship between models.
I have "Teams" which play against each other in "Matches"
The tables currently look like this although I might have to make some changes to the matches table.
Teams:
id | Name
1 | some-name#1
2 | some-name#2
3 | some-name#3
Matches
id | team_id1 | team_id2 | result1 | result2
1 | 2 | 3 | -1 | -1
In this example the team with id 2 plays against team with id 3. The result is not yet entered and therefore set to "-1" for both. If Team 2 would loose against Team 3 with a score of "3:7" then result1 would be "3" and result2 "7"
A Team has many matches and one match belongs to two teams.
How can i model the relationships in Rails?
team.rb
has_many :matches
matches.rb
belongs_to :team_one, :foreign_key => "team_id1", :class_name => "Team"
belongs_to :team_two, :foreign_key => "team_id2", :class_name => "Team"
In this case suppose you want to find team one's name and team two's name from the object of matches do following
match = match.find(1)
match.team_one.name ### gives -> some-name#2
match.team_two.name ### gives -> some-name#3

Rails - Multiple Index Key Association

There seem to be a number of ways to handle a multiple foreign key association. Each way I have approached this has their draw backs, and as I am new to Rails I am convinced others have come across a similar scenario and I am probably working on something solved long ago.
My question is:
What would be an efficient way of handling a multiple index key association, while still retaining all other Rails sql modifiers (such as :include etc)?
My scenario is:
I have a table association as follows (simplified), which is used to connect people to other people via links:
People
+----+-----------+
| id | name |
+----+-----------+
| 1 | Joe |
+----+-----------+
| 2 | Sally |
+----+-----------+
| 3 | Bob |
+----+-----------+
Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1 | 2 | 1 |
+----+-----------+---------+
| 2 | 1 | 3 |
+----+-----------+---------+
| 3 | 3 | 2 |
+----+-----------+---------+
From row 1 of the above Links table, one can see that a Person (Sally = 2) is linked to another Person (Joe = 1).
It is easy for me to find all of a Persons Links if my foreign key was "origin_id". But this would only show People originating a Link. In my scenario I need to see all links regardless if they were originated or received by a Person. If for example I were to ask for all of Sally's links (Sally = 2), the result I would want would be:
Links
+----+-----------+---------+
| id | origin_id | rcvd_id |
+----+-----------+---------+
| 1 | 2 | 1 |
+----+-----------+---------+
| 3 | 3 | 2 |
+----+-----------+---------+
Hence I have 2 index keys, both "origin_id" and "rcvd_id".
One way this could be solved is with a Method:
class Person < ActiveRecord::Base
has_many :link_origins, :class_name => "Link", :foreign_key => :origin_id, :dependent => :destroy
has_many :link_rcvds, :class_name => "Link", :foreign_key => :rcvd_id, :dependent => :destroy
def links
origin_person + rcvd_person
end
However, this is not efficient. For example this requires the entire collection to be gathered from the database and only then does the paginate method work (I am using the will_paginate gem), which defeats the point as paginate should speed up the process by limiting the number of records called. Not limit the records after the entire collection is already done.
Also, the above will not allow me to call for example, Joe.links(:first).origin_id.name. Not exactly this code but meaning I could not call the Person details on the origin_id of a selected link, as the links method does not know that origin_id is related to the People table.
So far the most workable solution seems to be the :finder_sql.
class Person < ActiveRecord::Base
has_many :links, :finder_sql => 'SELECT * FROM links WHERE (links.origin_id = #{id} or links.rcvd_id = #{id})'
This gives all links where the Person_id matches either the Links.origin_id or the Links.rcvd_id.
The down side of this option, is that using :finder_sql, rules out all the other sql modifiers since Rails
doesn't know how to parse and modify the SQL you provide. For example I would not be able to use the :include option with the :finder_sql.
So, right now I am using the :finder_sql, solution. But it seems there might be a away of getting this association done in such a way that I don't need a :finder_sql. For example, is there a way to write a custom sql string while retaining the Rails sql modifiers that Active Record supplies.
Any ideas on the above?
I did find the solution to this, however it turned out I was probably asking the wrong question. I have not found away to have multiple index keys as I asked without implementing some custom sql which breaks different rails helpers.
I guess my question still stands, but how I did resolve this was to look at the problem differently. I just created the associations as they are:
belongs_to :rcvd_person, :class_name => 'Person', :foreign_key => :rcvd_id
belongs_to :origin_person, :class_name => 'Person', :foreign_key => :origin_id
And a custom sql statement:
class Person...
has_many :links, :finder_sql => 'SELECT * FROM links WHERE origin_id = #{id} OR rcvd_id = #{id}'
end
Then I was able to manipulate the records how I wanted in my view. In case anyone else is doing something similar, I did:
<% person.links.each do |link| %>
<% if link.origin_id == person.id %>
<%= link.rcvd_person.given_name %>
<% else %>
<%= link.origin_person.given_name %>
<% end %>
<% end %>
I'm not sure you can really support an association with multiple keys in the same table because Rails won't know which key to set if you attempt to create a relationship.
However, if you just want person.links, Rails 3 provides a way that is better than :finder_sql
class Link
def self.by_person(person)
where("origin_id => :person_id OR rcvd_id => :person_id", :person_id => person.id
end
end
class Person < ActiveRecord::Base
# You can include the has_many relationships if you want, or not
def links
Link.by_person(self)
end
end
This lets you do things like #person.links.limit(3) (which currently appears to be broken when using :finder_sql)

what is a RoR best practice? match by id or different column?

I had a terrible morning. Lots of emails floating around about why things don't work. Upon investigating I found that there is a data mismatch which is causing errors.
Scenario
Customer and Address are two tables.
Customer contains
class Customer < ActiveRecord::Base
has_one :address, :foreign_key => "id"
end
Address Contains
class Address < ActiveRecord::Base
belongs_to :customer, :foreign_key => "cid"
end
So the two tables match on id which is the default and that column is auto incremented.
Problem
on the edit Page we have some code like this.
params[:line1] = #customer.first.address.line1
It fails because no matching record is found for a customer in the address table. I don't know why this is happening. It seems that over time a lot of records did not get added to Address table. Now problem is that when a new Customer is added (say with id 500) the Address will be added with some other id (say 425) ...now you don't know which address belongs to which customer.
Question
Being new to Rails, I am asking whether it is always considered good to create an extra column for joining of the records, rather than depending on the column that is automatically incremented? If I had a seperate column in Address table where I would manually insert the recently added customers id then this issue would not have come up.
That has_one-belongs_to relationship should result in the "belonging" model having the key of the "having" model. Or, in other words, the :foreign_key clause should be the same in both models.
If I have these:
class Customer < ActiveRecord::Base
has_one :address, :foreign_key => 'cid' # note foreign_key same as in Address
end
class Address < ActiveRecord::Base
belongs_to :customer, :foreign_key => 'cid' # note foreign_key same as in Customer
end
then I can do this:
>> cust = Customer.create(:name=>'Mr Custard')
+----+------------+
| id | name |
+----+------------+
| 1 | Mr Custard |
+----+------------+
1 row in set
>> add = cust.create_address(:line_1 => '42 Some Street', :line_2 => 'Some where')
+----+-----+----------------+------------+
| id | cid | line_1 | line_2 |
+----+-----+----------------+------------+
| 1 | 1 | 42 Some Street | Some where |
+----+-----+----------------+------------+
1 row in set
checking:
>> Customer.first.address
+----+-----+----------------+------------+
| id | cid | line_1 | line_2 |
+----+-----+----------------+------------+
| 1 | 1 | 42 Some Street | Some where |
+----+-----+----------------+------------+
1 row in set
>> Address.first.customer
+----+------------+
| id | name |
+----+------------+
| 1 | Mr Custard |
+----+------------+
and my database looks like this:
sqlite> select * from customers;
1|Mr Custard
sqlite> select * from addresses;
1|1|42 Some Street|Some where
(the nice table output for ActiveRecord results comes from Hirb, by the way)
The Rails' convention is for each table to have an auto-incrementing integer primary key column named id and additionally—in your example—for the addresses table to have a non auto-incrementing integer foreign key column named customer_id. As the name implies, this holds the primary key value from the associated record in the customers table.
If you follow these rules then there's no need to specify a :foreign_key option on the associations.

Resources