I'm using Rails 4.2, Chartkick & GroupDate gems, To display statistics about purchased orders.
order.rb
class Order < ActiveRecord::Base
...
has_many :ice_creams
...
end
ice_cream.rb
class IceCream < ActiveRecord::Base
...
has_and_belongs_to_many :flavors
has_many :added_extras
has_many :extras, :through => :added_extras
belongs_to :order
...
end
extra.rb
class Extra < ActiveRecord::Base
...
has_many :added_extras
has_many :extras, :through => :added_extras
...
end
My intention was to display a chart showing the following:
Count of overall purchased Orders or Ice Creames grouped by Extra's Name.
In another expression, The last chart is intended to display the most popular Extras customers consume.
graphs.html.erb
<%= line_chart IceCreams.all.joins(:added_extras).group(:extra_id).count %>
The chart actually works but I cant' get it to display the Extra name, Or even group by Extra name.
Anyway my question is...
How to be able to display the Extra name instead of id?
You can join Extra table through AddedExtra table. Try below:
IceCreams.joins(added_extras: :extra).group('extras.name').count
Related
I have models with deep associations in my Ruby on Rails API, sometimes 4 associations deep. For example:
Group has_many Subgroups has_many: Posts has_many: Comments
If I want to return Group.title with my comments, I need to say:
#comment.post.subgroup.group.title
Since this is way too many queries per Comment, I have added a column to the Comment table called group_title. This property is assigned when the Comment is created. Then every time the associated Group.title is updated, I have an after_update method on the Group model to update all associated Comment group_titles.
This seems like a lot of code to me and I find myself doing this often in this large scale app. Is there a way to link these 2 properties together to automatically update Comment.group_title every time its associated Group.title is updated?
I also had a similar relation hierarchy, and solved it (maybe there are better solutions) with joins.
Quarter belongs_to Detour belongs_to Forestry belongs_to Region
For a given detour, I find region name with one query.
Quarter.select("regions.name AS region_name, forestries.name as forestry_name, \
detours.name AS detour_name, quarters.*")
.joins(detour: [forestry: :region])
Sure, you can encapsulate it in a scope.
class Quarter
...
scope :with_all_parents, -> {
select("regions.name AS region_name, forestries.name as forestry_name, \
detours.name AS detour_name, quarters.*")
.joins(detour: [forestry: :region])
}
end
You can also use same approach.
class Comment
...
scope :with_group_titles, -> {
select("groups.title AS group_title, comments.*").joins(post: [subgroup: :group])
}
end
You can build hierarchies by using indirect associations:
class Group
has_many :subgroups
has_many :posts, through: :subgroups
has_many :comments, through: :posts
end
class Subgroup
belongs_to :group
has_many :posts
has_many :comments, through: :posts
end
class Post
belongs_to :subgroup
has_one :group, through: :subgroup
has_many :comments
end
class Comment
belongs_to :post
has_one :subgroup, through: :post
has_one :group, through: :post
end
The has_many :through Association
The has_one :through Association
This allows you to go from any end and rails will handle joining for you.
For example you can do:
#comment.group.title
Or do eager loading without passing a nested hash:
#comment = Comment.eager_load(:group).find(params[:id])
This however does not completely solve the performance issues related to joining deep nested hierarchies. This will still produce a monster of a join across four tables.
If you want to cache the title on the comments table you can use an ActiveRecord callback or you can define a database trigger procedure.
class Group
after_save :update_comments!
def update_comments!
self.comments.update_all(group_title: self.title)
end
end
You can do this by updating all the Comments from one side.
class Group
after_update do
Comment.joins(post: [subgroup: :group]).where("groups.title=?", self.title).update_all(group_title: self.title)
end
end
I have a User model which has_many Portfolios, which has_many Assets which has_and_belongs_to_many AssetHistories.
Basically User 1 might have Google in their portfolio and User 2 might also have Google in their portfolio. Why populate the database with duplicate lines of stock price history for Google when I can have a many-to-many (HABTM) relationship. However what throws me off is what to put for asset_id in the AssetHistory model when it will be multiple values. I.e. it needs to reference both user 1 and user 2. User 1's Google might be asset.id 1 and User 2's Google might be asset.id 2. Therefore how do the entries in the AssetHistory model reference both the ids?
It seems pretty clear that asset_id can't be 2 values simultaneously but I can't wrap my head around this. Am I supposed to use a foreign_key and make Google the key? If so, I still have issues in my Asset model for what entry to put for Asset_History_id, because the asset Google, will have maybe 30 lines of stock price history. Each stock price history would be a different Asset_History_id.
Can someone help explain what I'm doing wrong?
Note that I am using after_save in my asset model to populate the asset price histories. I.e. when someone adds an Asset, it populates the asset_history, but it doesn't populate the asset_history_id field in the Asset model and it doesn't populate the asset_id in the AssetHistory model because I'm at a lost on what to do there.
My asset model has:
class Asset < ActiveRecord::Base
attr_accessible :asset_symbol, :shares, :cost, :date_purchased, :asset_history_id
belongs_to :portfolio
has_and_belongs_to_many :asset_histories
after_save populatepricehistory
private
def populatepricehistory
#uses an api to download price data as an array and imports it to AssetHistory...
#I expect something should go here to fill out the asset_history_id field in the Asset Model
#while simultaneously filling out the asset_id in the AssetHistory model
end
end
Asset History model
class AssetHistory < ActiveRecord::Base
attr_accessible :close, :date, :asset_id, :asset_symbol
has_and_belongs_to_many :assets
end
Migration for AssetHistoryAsset join table
class AssetHistoryAssetJoinTable < ActiveRecord::Migration
def up
create_table :asset_histories_assets, :id=> false do |t|
t.integer :asset_id
t.integer :asset_history_id
end
end
def down
drop_table :asset_histories_assets
end
end
My suggestion would be this:
class User < ActiveRecord::Base
has_many :assets, :through => :porfolios
has_many :porfolios
end
class Porfolio < ActiveRecord::Base
has_many :assets
has_many :users
end
class Asset < ActiveRecord::Base
has_many :users, :through => :portfolios
has_many :portfolios
has_and_belongs_to_many :asset_histories
end
By the way, do you really need a many-to-many relationship between Asset and AssetHistory? I would imagine each instance of AssetHistory to refer to only one Asset, probably by means of belongs_to :asset / has_one :asset_history.
I want to make a cooking site but don't know the correct was to build database.
My models are: Recipe and Ingredient.
Ingredient in recipe should be autocomplete field. The problem is that user can put there any text. ("cucumber" or "cucamber") and it will be different ingredients.
I want to make a search by ingredients and links to them. What is the best way to do it?
A recipe has many items, which in turn keeps a reference to an ingredient, an amount and a measure type. So you can go with:
rails generate model Recipe name:string description:text
rails generate model Item recipe:references ingredient:references amount:decimal measure:string
rails generate model Ingredient name:string
and then add to your classes:
class Recipe < ActiveRecord::Base
has_many :items
has_many :ingredients, :through => :items
# this allows things like #recipes = Recipe.using("cucumber")
scope :using, lambda do |text|
joins(:ingredients).where("ingredients.name LIKE ?", "%#{text}%")
end
end
class Item < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
VALID_MEASURES = %w[oz kg tbsp] # use for "select" tags in forms
validates :measure, :inclusion => VALID_MEASURES
end
class Ingredient < ActiveRecord::Base
belongs_to :item
end
From here you start building your views, autocomplete, whatever your imagination allows.
I want to create a random pack of 15 cards which should be invoked in the cardpacks_controller on create. I have the following models:
Card:
class Card < ActiveRecord::Base
# relations
has_many :cardpacks, through: :cardpackcards
belongs_to :cardset
end
Cardpack:
class Cardpack < ActiveRecord::Base
#relations
has_many :cards, through: :cardpackcards
belongs_to :cardset
# accept attributes
accepts_nested_attributes_for :cards
end
Cardpackcards:
class Cardpackcard < ActiveRecord::Base
#relations
belongs_to :card
belongs_to :cardpack
end
Cardsets:
class Cardset < ActiveRecord::Base
#relations
has_many :cards
has_many :cardsets
end
How can I create 15 Cardpackcards records with random card_id values and with the same cardpack_id (so they belong to the same pack)
I have watched the complex form series tutorial but it gives me no comprehension as how to tackle this problem.
I hope anyone can help me solve this problem and give me more insight in the rails language.
Thanks,
Erik
Depending on the database system you might be able to use an order random clause to find 15 random records. For example, in Postgres:
Model.order("RANDOM()").limit(15)
Given the random models, you can add a before_create method that will setup the associations.
If the Cardpackcard model doesn't do anything but provide a matching between cards and cardpacks, you could use a has_and_belongs_to_many association instead, which would simplify things a bit.
Without it, the controller code might look something like this:
cardset = Cardset.find(params[:cardset_id])
cardpack = Cardpack.create(:cardset => cardset)
15.times do
cardpack.cardpackcards.create(:card => Card.create(:cardset => cardset))
end
Im very new to Ruby on Rails 3 and ActiveRecord and seem to have been thrown in at the deep end at work. Im struggling to get to grips with querying data from multiple tables using joins.
A lot of the examples Ive seen either seem to be based on much simpler queries or use < rails 3 syntax.
Given that I know the business_unit_group_id and have the following associations how would I query a list of all related Items and ItemSellingPrices?
class BusinessUnitGroup < ActiveRecord::Base
has_many :business_unit_group_items
end
class BusinessUnitGroupItem < ActiveRecord::Base
belongs_to :business_unit_group
belongs_to :item
belongs_to :item_selling_price
end
class Item < ActiveRecord::Base
has_many :business_unit_group_items
end
class ItemSellingPrice < ActiveRecord::Base
has_many :business_unit_group_items
end
I'm confused as to whether I need to explicity specify any joins in the query since the associations are in place.
Basically, you do not need to specify the joins:
# This gives you all the BusinessUnitGroupItems for that BusinessUnitGroup
BusinessUnitGroup.find(id).business_unit_group_items
# BusinessUnitGroupItem seems to be a rich join table so you might
# be iterested in the items directly:
class BusinessUnitGroup < ActiveRecord::Base
has_many :items through => :business_unit_group_items
# and/or
has_many :item_selling_prices, through => :business_unit_group_items
...
end
# Then this gives you the items and prices for that BusinessUnitGroup:
BusinessUnitGroup.find(id).items
BusinessUnitGroup.find(id).item_selling_prices
# If you want to iterate over all items and their prices within one
# BusinessUnitGroup, try this:
group = BusinessUnitGroup.include(
:business_unit_group_item => [:items, :item_selling_prices]
).find(id)
# which preloads the items and item prices so while iterating,
# no more database queries occur