How to create links between two tables - ruby-on-rails

Ok so I'm starting on normalising my database. Currently I have one model "Products" which is populated with about 60,000 products via a data feed (XML), which contains a product with a category name and a merchant name. I want to split these into 3 models; products, categories and merchants.
Each product has one category and one merchant so the natural idea is to create these models:
category_id | category_name
merchant_id | merchant_name
I can work out the code to associate between the models i.e. has_one, belongs_to etc but I'm struggling to work out to automatically associate a new Product with a category and a merchant programatically.
I've seen examples in books where your start with an empty database and that seems pretty straightforward. However, I'm starting off with a full database and a list of Category names.
Here is my product creation statement which is working great:
Product.create(:name => node.xpath("./text/name/text()").inner_text.downcase,
:description => node.xpath("./text/desc/text()").inner_text,
:brand => node.xpath("./brand/text()").inner_text,
:merchant => node.xpath("../#name").inner_text,
:category => node.xpath("./cat/text()").inner_text.downcase,
:price => "£" + node.xpath("./price/btext()").inner_text)
Would I need to do something like this, see the :category line, (i know the following is wrong btw!)...
Product.create(:name => node.xpath("./text/name/text()").inner_text.downcase,
:description => node.xpath("./text/desc/text()").inner_text,
:brand => node.xpath("./brand/text()").inner_text,
:merchant => node.xpath("../#name").inner_text,
:category => << Category.find_by_name(node.xpath("./cat/text()").inner_text.downcase),
:price => "£" + node.xpath("./price/btext()").inner_text)
Any ideas? Does this even make sense!?

Assuming the columns are called category_name and merchant_name, and you've set up the associations on Category and Merchant, you could do something like this:
Product.all do |product|
product.category = Category.find_or_create_by_category_name(product.category_name)
product.merchant = Merchant.find_or_create_by_merchant_name(product.merchant_name)
product.save!
end
It will take a while, so for large datasets you might need a better solution.
So would this actually set the :category value in the products table to a category_id or set the value to the category_name?
.find_or_create_by does a find on the attribute and returns the matching row, or creates one if it does not exist. When creating the association via `.category=, Rails will set the foreign key to match the id of the row in the categories table.
So to answer your question more directly:
Product.create(:category=>Category.find_or_create_by_name("Magic Beans"))
is like doing this:
category = Category.find_by_name("Magic Beans")
if category.nil?
category = Category.create(:name=>"Magic Beans")
end
product = Product.new
product.category = category
product.save
where the penultimate step sets the foreign key category_id to the value category.id. By convention associations are set up such that the foreign key is the model name suffixed with _id, so your products table should have both category_id and merchant_id.

Related

ActiveScaffold or ActiveRecord how to search on an associated model's column

I have following models
Customer: name:string, phone:string
has_many :sales
Sale: amount:float, item_name:string
belongs_to :customer
Now in ActiveScaffold I get a simple single line input box to search for sales.
What I need to do is, be able to search sales by the customer name as well.
There should be a way to do this with ActiveScaffold, or atleast with native active record functionality.
One way I can think of is, adding the customer name as a column and populate it along with sale while it is created.Not sure what the best approach to achieve this should be.
Edit: Adding info from comment:
Is it possible to define something like
Sales
searchable_columns :amount, :association => {:customer => name}, :as => yumyum_column
now when i search by
Sale.where(:yumyum_column => 1000)
or
Sale.where(:yumyum_column => "Customer name")
both will return the same record.
Try something like this:
# Sales Controller
config.columns[:customer].search_sql = "customers.name"
config.search.columns = [:item_name, :customer] # Search by sale's item_name or customers's name.
To show the customer name in the list of results try something like:
# Customer model
def to_label
name
end
# Sales Controller
config.list.columns = [:item_name, :customer]
For more info, see the API: Search page
If you have (and you should have) a customer_id in your sales table, then you can query sales with any customer attribute you like with a simple join:
Sales.joins(:customers).where(:'customers.name' => :john)
which translates to the following SQL
"SELECT `sales`.* FROM `sales` INNER JOIN `customers` ON `sales`.`customer_id` = `customers`.`id` WHERE `customers`.`name` = 'john'"

ActiveRecord group by on a join

Really been struggling trying to get a group by to work when I have to join to another table. I can get the group by to work when I don't join, but when I want to group by a column on the other table I start having problems.
Tables:
Book
id, category_id
Category
id, name
ActiveRecord schema:
class Category < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :category
end
I am trying to get a group by on a count of categories. I.E. I want to know how many books are in each category.
I have tried numerous things, here is the latest,
books = Book.joins(:category).where(:select => 'count(books.id), Category.name', :group => 'Category.name')
I am looking to get something back like
[{:name => fiction, :count => 12}, {:name => non-fiction, :count => 4}]
Any ideas?
Thanks in advance!
How about this:
Category.joins(:books).group("categories.id").count
It should return an array of key/value pairs, where the key represents the category id, and the value represents the count of books associated with that category.
If you're just after the count of books in each category, the association methods you get from the has_many association may be enough (check out the Association Basics guide). You can get the number of books that belong to a particular category using
#category.books.size
If you wanted to build the array you described, you could build it yourself with something like:
array = Categories.all.map { |cat| { name: cat.name, count: cat.books.size } }
As an extra point, if you're likely to be looking up the number of books in a category frequently, you may also want to consider using a counter cache so getting the count of books in a category doesn't require an additional trip to the database. To do that, you'd need to make the following change in your books model:
# books.rb
belongs_to :category, counter_cache: true
And create a migration to add and initialize the column to be used by the counter cache:
class AddBooksCountToCategories < ActiveRecord::Migration
def change
add_column :categories, :books_count, :integer, default: 0, null: false
Category.all.each do |cat|
Category.reset_counters(cat.id, :books)
end
end
end
EDIT: After some experimentation, the following should give you close to what you want:
counts = Category.joins(:books).count(group: 'categories.name')
That will return a hash with the category name as keys and the counts as values. You could use .map { |k, v| { name: k, count: v } } to then get it to exactly the format you specified in your question.
I would keep an eye on something like that though -- once you have a large enough number of books, the join could slow things down somewhat. Using counter_cache will always be the most performant, and for a large enough number of books eager loading with two separate queries may also give you better performance (which was the reason eager loading using includes changed from using a joins to multiple queries in Rails 2.1).

Data in loop from belongs_to relationship

I have house with apartments (belongs_to house). The summary shows the house with the apartments with some info as short description and price range. The code i have is this(works fine)
- house.appartments.each do |a|
%li
%a.main_img
= link_to image_tag(a.attachments.first.file.url(:thumb), :height=>"93px", :width=>"135px", :class => "box"), apartment_path(a)
%br/
= link_to "#{a.name}", apartment_path(a), :class => "link_homepage"
Description: #{a.short_desc}
Price range: #{a.price_range}
I added a new model price with belongs_to relation to apartment. In this model/db the prices/rents data is stored of the apartments.
Question - Instead of a.price_range (apartment db-table) i want the data from the new price model/db-table in the summary.
If I'm understanding correctly, you have a model named something like House which has an association with Apartment which then has an association to a new model you made called something like ModelPrice? If so...
I assume in your new table ModelPrice you have a column for apartment_id or whatever the apartment ID column would be named in order to setup the association to the Apartment model.
Can you do a.model_price.price?
Of course, the names of each object would change based on the actual names of your new model/table name and the price column that is within it.

Rails HABTM joining with another condition

I am trying to get a list, and I will use books as an example.
class Book < ActiveRecord::Base
belongs_to :type
has_and_belongs_to_many :genres
end
class Genre < ActiveRecord::Base
has_and_belongs_to_many :books
end
So in this example I want to show a list of all Genres, but it the first column should be the type. So, if say a genre is "Space", the types could be "Non-fiction" and "Fiction", and it would show:
Type Genre
Fiction Space
Non-fiction Space
The Genre table has only "id", "name", and "description", the join table genres_books has "genre_id" and "book_id", and the Book table has "type_id" and "id". I am having trouble getting this to work however.
I know the sql code I would need which would be:
SELECT distinct genres.name, books.type_id FROM `genres` INNER JOIN genres_books ON genres.id = genres_books.genre_id INNER JOIN books ON genres_books.book_id = books.id order by genres.name
and I found I could do
#genre = Genre.all
#genre.each do |genre|
#type = genre.book.find(:all, :select => 'type_id', :group => 'type_id')
#type.each do |type|
and this would let me see the type along with each genre and print them out, but I couldn't really work with them all at once. I think what would be ideal is if at the Genre.all statement I could somehow group them there so I can keep the genre/type combinations together and work with them further down the road. I was trying to do something along the lines of:
#genres = Genre.find(:all, :include => :books, :select => 'DISTINCT genres.name, genres.description, books.product_id', :conditions => [Genre.book_id = :books.id, Book.genres.id = :genres.id] )
But at this point I am running around in circles and not getting anywhere. Do I need to be using has_many :through?
The following examples use your models, defined above. You should use scopes to push associations back into the model (alternately you can just define class methods on the model). This helps keep your record-fetching calls in check and helps you stick within the Law of Demeter.
Get a list of Books, eagerly loading each book's Type and Genres, without conditions:
def Book < ActiveRecord::Base
scope :with_types_and_genres, include(:type, :genres)
end
#books = Book.with_types_and_genres #=> [ * a bunch of book objects * ]
Once you have that, if I understand your goal, you can just do some in-Ruby grouping to corral your Books into the structure that you need to pass to your view.
#books_by_type = #books.group_by { |book| book.type }
# or the same line, more concisely
#books_by_type = #books.group_by &:type
#books_by_type.each_pair do |type, book|
puts "#{book.genre.name} by #{book.author} (#{type.name})"
end

How to sort by associated model within given value?

I have model Item and model Stats.
Item
has_many :stats
Stat
belongs_to :items
In the model (e.g. mysql table) Stat there is 3 fields:
rating
skin_id
item_id
So for Stat, it could be, like:
#item.stats => Array of stats for records with item_id = 1, with a differer skin_id
I need to sort all items, for a given skin_id by 'rating'.
Something like:
#items = Item.all.order('stats[currtnt_skin.id] DESC') (of course it doesn't work)
In other words i need to sort within array of:
#stats = #items.stats[current_skin.id]
#items.order (... by #stats ...)
How it could be done?
Firstly I'm presuming by belongs_to :items you mean belongs_to :item (singular) given the presence of the item_id foreign key.
Secondly, to solve your specific query you can use:
Stat.where(:skin_id => skin_id).joins(:item).order("items.rating DESC")
However, if skin_id refers to another model - i.e. Stat belongs_to :skin and Skin has_many :stats then it may make more sense to start from there:
skin = Skin.find(1)
stats = skin.stats.order("rating DESC").includes(:item)
To get the items then just loop through them:
stats = skin.stats.order("rating DESC").includes(:item)
stats.each do |stat|
stat.item
end
F
#items = Item.join(:stats).order('skin_id DESC')
I believe, though I might be mistaken that joining the table will do so on the association you've defined.
in rails 3 it will be something like:
Item.includes("stats").order("stats.skin_id desc")
Have you tried this ?
Item.includes("stats").where('stats.skin_id = ?', 1).order("stats.rating desc")

Resources