Using single model for multiple tables - ruby-on-rails

I am wondering if it is possible to combine columns from of different tables and use it as one model in Rails. I have two tables below, one holds generic columns and other specialize columns.
posts
--------------
id
title
description
created_at
updated_at
jobs
--------------
post_id
category_id
job_type
duration
salary
In Rails model,
class Job < ActiveRecord::Base
#
end
On saving Job model should save columns in respective tables. I thought about using single table inheritance (STI) but look like I can't split columns in multiple tables with this approach.

Hello you just need to use accepts_nested_attributes_for, then you can fill column of post on saving jobs using the posts_attributes key.
Adding posts to job
job[posts_attributes] = [{ :title => "test", :description => "Lorem ipsum"}]
Deleting posts from job
job[posts_attributes = [{ :id:20, :_destroy => true}]
Hope that will help you ;)

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'"

Implementing model subclasses - the correct way

I have an Event model and I want to create two subclasses of it: FixedEvent and TasksEvent
Event model has these attributes:
uid :string
title :string
starts_at :datetime
ends_at :datetime
FixedEvent inherits from Event and also has this attribute:
all_day :boolean
TasksEvent inherits from Event and has these attributes:
task_id :integer
occurrence :integer
(Occurrence attribute is a special way for tasks to say: Do this task two/three/x times. Occurrence represents which occurrence of the task this is (e.g. this is second time user is performing the task))
I spent the whole day yesterday reading about Single Table Inheritance and Polymorphic Associations and I'm still not 100% sure what's the correct way to implement this in Rails.
Single Table Inheritance leaves me with lot of null values in my database as I'll end up having one table with: uid, title, starts_at, ends_at, all_day, task_id, occurence, type.
Will this behaviour make server response slower as rails will fetch more (null) data for every event query?
On the other hand, Polymorphic Associations look more like I'm adding some extra functionality to model, rather than subclassing it. Also it creates more tables (three in this case) in the db:
events:
id, uid, title, starts_at, ends_at,
created_at, updated_at,
event_type_id, event_type_type
(suggest better naming for type if something comes to your mind)
fixed_events:
id
all_day
created_at
updated_at
tasks_events:
id
task_id
occurrence
created_at
updated_at
Will this behaviour make server response slower as rails will have to do several db joins every time I want to fetch all FixedEvent/TasksEvent attributes?
Also, how can I create new ActiveRecord objects using STI and/or Polymorphic Associations?
I tried something like this for Polymorphic Association:
def new
#fixed_event = FixedEvent.new
#fixed_event.build_event
respond_to :html
end
And then in form_for:
= f.fields_for :event do |event|
.field
= event.label :title
= event.text_field :title
.field
= event.label :starts_at
= event.datetime_select :starts_at
.field
= event.label :ends_at
= event.datetime_select :ends_at
.field
= event.label :description
= event.text_field :description
.field
= event.label :uid
= event.text_field :uid
I had to add these two things in FixedEvent and TasksEvent and it worked:
attr_accessible :event_attributes
accepts_nested_attributes_for :event
Is this the correct way to do it or STI is better (or any other solution)?
You want to go with STI as it is simple and will be much faster than loading associations. Loading a bunch of null values is nothing to be concerned about, performance wise. On the other hand, joins (or even eager loading) is much more likely to cause performance problems once your events table contains tens of thousands of entries.
If selecting all of the columns ever becomes a problem for you, you can always just select a subset of all columns with:
FixedEvent.select('foo, bar').all
It seems like you already understand STI so I don't have to teach you how to do it here. It's simple, really—just create the "events" table with "type" column, and Event class, then subclass it.

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.

How to create links between two tables

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.

Resources