My controller is retrieving the follow:
def index
#segments = Segment.all
#products = Product.all
end
But it is not what I want. I want to iterate products based on its segment_id. Something like this:
def index
#segments = Segment.all
#products = Product.where(segment_id: x)
end
The problem is: how to pass x from view to controller?
My view is:
- #segments.each do |s|
- #products.each do |p|
This is a product from #{s.name}.
Ok, where's the issue? I'm not showing specific products of specific segments. I'm showing products of all segments. What I need is something like this:
- #segments.each do |s|
- #products(segment_id: s.id).each do |p|
This is a product from #{s.name}.
Can you understand?
Set up has_many association between Segment and Product model.
class Segment < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :segment
end
Add segment_id in the products table.
Generate the migration with
rails g migration add_segment_id_to_products segment_id:integer:index
Run rake db:migrate
Setting up the association would give you dynamic method products for an instance of segment which you can use in the view for iteration.
Then update the index action as:
def index
#segments = Segment.all
end
Update the view as:
- #segments.each do |s|
- s.products.each do |p|
This is a product from #{s.name}.
Would this do?
def index
#segments = Segment.includes(:products).all
end
And in your view:
- #segments.each do |s|
- s.products.each do |p|
This is product #{p.name} from segment #{s.name}
You can add where conditions to the query for both segmens and products if you want to filter them in any way you need like this.
Segment.includes(:products).where(:seg_val => 'something', :product => [:prod_val => 'other']).all
Related
I have a site where customers can purchase tickets for an event and created a Reporter::EventTickets class to generate a hash for the relevant information I need. Reporter::EventTickets is called in a Reports controller, and ultimately I want to view this on an admin report page.
I cannot get the information to display on my table in my haml view file!
I've tried everything I can think and am at a complete loss for what to do.
Information is being saved to the database (PostgreSQL) successfully and the EventTickets class is transforming data properly (passing RSpec tests).
Code:
class Reporter::EventTickets
include Virtus.model
def events
products.map do |product|
line_items = LineItem.includes(:order)
.where("orders.completed_at IS NOT NULL")
.where(purchasable_type: "Product", purchasable_id: product.id)
.references(:order)
ticket_purchases = line_items.map do |line_item|
order = line_item.order
[order.bill_address_full_name, order.email, line_item.quantity]
end
total = ticket_purchases.reduce(0) { |sum, purchase| sum + purchase.last }
{
date: product.event_date,
name: product.name,
total: total,
purchases: ticket_purchases.sort
}
end
end
private
def products
Ticket.where("event_date >= ?", week_ago_in_time_zone).order("event_date ASC")
end
def week_ago_in_time_zone
(Time.current - 7.days).to_date
end
end
Controller:
def event_tickets
#reporter = Reporter::EventTickets.new
#csv_link = admin_reports_event_tickets_path(format: :csv)
respond_with #reporter
end
Models:
class Order < ActiveRecord::Base
has_many :line_items, dependent: :destroy
class LineItem < ActiveRecord::Base
belongs_to :order, touch: true
belongs_to :purchasable, polymorphic: true
class Product < ActiveRecord::Base
include Purchasable
has_many :line_items, as: :purchasable
View:
= render partial: "admin/shared/header", locals: {title: "Event Ticket Purchases"}
.container-fluid.padded
= render partial: "admin/shared/notifications"
.row-fluid.hidden-print
= link_to "Download CSV", #csv_link, class: "btn btn-blue"
.row-fluid
- #reporter.events.each do |event|
%h3
= event[:date].strftime("%_m/%e")
= event[:name]
.box
%table.table.table-normal
%thead
%tr
%td Name
%td Email
%td Tickets
%tbody
- event[:purchases].each do |purchase|
%tr
- purchase.each do |column|
%td= column
%tr
%td{:colspan => "2"}
%b TOTAL
%td
%b= event[:total]
There are no errors from Rails and the page loads with the title and button. The table just does not populate.
As debugged in comments, your code is fine. With live data, this:
Ticket.where("event_date >= ?", week_ago_in_time_zone).order("event_date ASC")
...however, is returning an empty result set. So this:
products.map do |product|
...
end
...is returning an empty array.
Your RSpec test passed because you had Ticket records in your test data that met the criteria and, therefore, products.map was returning a non-empty array.
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")
I'm trying to work on this Rails app which has the following objectives:
/foods/ - render a list of food categories (eg: Breads, Dairy, Biscuits...etc)
/foods/breads/ - render all Foods that are within the food category "Breads"
foods/breads/bagel - render a detailed view of the properties of the Food (in this example a Bagel).
Currently I have two models with associated controllers:
Foods - contains a list of foods (eg: bagel, rice, toast, rich tea biscuit...etc) and is set up to belongs_to a single Food Cat
Food Categories - a list of categories such as "Dairy", "Breads"...etc & is set up to has_many :foods
I'm really stuck on how to achieve my objectives. I really need advice on routing, controller actions and views.
Any suggestions?
In your routes.rb file, I would do the following:
match 'foods' => 'FoodCategories#index'
match 'foods/:category' => 'Foods#index'
match 'foods/:category/:name' => 'Foods#show'
I would then create a scope for Foods by category:
class Food
scope :by_category, lambda{ |category| joins(:categories).where('categories.name = ?', category) }
end
I would then have 2 actions in your FoodsController:
class FoodsController
def index
#foods = Food.by_category(params[:category])
end
def show
#foods = Food.by_category(params[:category]).where('foods.name = ?', params[:name])
end
end
And a single action in your FoodCategoriesController:
class FoodCategories
def index
#categories = Category.where(name: params[:category])
end
end
That should leave you with having to implement 3 views: categories/index, foods/index and foods/show.
You should have a FoodsController and a FoodCategoriesController dealing with Food and FoodCategory models.
if you follow the RESTful approache, then the routes neccessary to achieve the url configuration you listed will be as follows:
match '/foods' => 'food_categories#index'
match '/foods/:category_id' => 'food_categories#show'
match '/foods/:category_id/:food_id' => 'foods#show'
Your FoodCategoriesController will have methods index method which lists all the categories by performing FoodCategory.find :all lookup, as well as show method which will lookup a FoodCategory based on provided :category_id and display all the foods associated with it via has_many relationship.
Your FoodController will have a show method that will at least take the :food_id and look up the Food instance associated with it. :category_id is not really neccessary here, but its a nice routing sugar.
I have a Product model:
class Product < ActiveRecord::Base
belongs_to :subcategory
define_index do
# fields
indexes subcategory.name, :as => :subcategory, :sortable => true, :facet => true
# attributes
has subcategory_id, created_at, updated_at
#properties
set_property :delta => true
Now, suppose that a user updates a subcategory name, which is the proper way to update the products delta index?
According to this documentation: http://freelancing-god.github.com/ts/en/deltas.html, a save message should be sent to the product, so in this case I should go for each product related with the subcategory and send the save message, something like this:
class Subcategory < ActiveRecord::Base
has_many :products
after_save :set_product_delta_flag
private
def set_product_delta_flag
products.each { |product|
product.delta = true
product.save
}
end
end
I think that this is overkilling because we have like 100.000 products per subcategory.
Is this the correct way to update the delta index? Am I missing something?
After adding this:
def set_product_delta_flag
Product.update_all ['delta = ?', true], ['subcategory_id = ?', id]
Product.index_delta
end
I'm always receiving this error:
NoMethodError (undefined method `index_delta' for #):
So, the solution to this problem was to send the message *define_indexes* to the Product model.
After fixing this issue, everything was ok, but the delta_index was not correctly updated, I needed to do save twice to the subcategory model.
So my final solution is this one:
after_commit :set_product_delta_flag
private
def set_product_delta_flag
Product.define_indexes
Product.update_all ['delta = ?', true], ['subcategory_id = ?', id]
Product.index_delta
end
Using after_commit and define_indexes is the correct solution? Its the only one that I've found.
Try the following instead:
def set_product_delta_flag
Product.update_all ['delta = ?', true], ['subcategory_id = ?', id]
Product.index_delta
end
A single SQL statement, a single delta re-indexing. Should perform far better :)
I have three models:
class Book < ActiveRecord::Base
has_many :collections
has_many :users, :through => :collections
end
class User < ActiveRecord::Base
has_many :collections
has_many :books, :through => :collections
end
class Collection < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
I'm trying to display a list of the books and have a link to either add or remove from the user's collection. I can't quite figure out the best syntax to do this.
For example, if I do the following:
Controller
class BooksController < ApplicationController
def index
#books = Book.all
end
end
View
...
<% if book.users.include?(current_user) %>
...
or obviously the inverse...
...
<% if current_user.books.include?(book) %>
...
Then queries are sent for each book to check on that include? which is wasteful. I was thinking of adding the users or collections to the :include on the Book.all, but I'm not sure this is the best way. Effectively all I need is the book object and just a boolean column of whether or not the current user has the book in their collection, but I'm not sure how to forumlate the query in order to do that.
Thanks in advance for your help.
-Damien
I have created a gem(select_extra_columns) for returning join/calculated/aggregate columns in a ActiveRecord finders. Using this gem, you will be able to get the book details and the flag indicating if the current user has the book in one query.
In your User model register the select_extra_columns feature.
class Book < ActiveRecord::Base
select_extra_columns
has_many :collections
has_many :users, :through => :collections
end
Now in your controller add this line:
#books = Book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:extra_columns => {:belongs_to_user => :boolean},
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
Now in your view you can do the following.
book.belongs_to_user?
You're going to to want 2 SQL queries, and O(1) based lookups (probably irrelevant, but it's the principle) to check if they have the book.
The initial calls.
#books = Book.all
#user = User.find(params[:id], :include => :collections)
Next, you're going to want to write the books the user has into a hash for constant time lookup (if people won't ever have many books, just doing an array.include? is fine).
#user_has_books = Hash.new
#user.collections.each{|c|#user_has_books[c.book_id] = true}
And on the display end:
#books.each do |book|
has_book = #user_has_books.has_key?(book.id)
end
I'd err away from caching the book_ids on the user object, simply because going this route can have some funny and unexpected consequences if you ever start serializing your user objects for whatever reason (i.e. memcached or a queue).
Edit: Loading intermediary collection instead of double loading books.
Essentially you need to make one call to get the book information and the Boolean flag indicating if the current user has the book. ActiveRecord finders doesn't allow you to return the join results from another table. We work around this problem by doing a trick.
In your Book model add this method.
def self.extended_book
self.columns # load the column definition
#extended_user ||= self.clone.tap do |klass|
klass.columns << (klass.columns_hash["belongs_to_user"] =
ActiveRecord::ConnectionAdapters::Column.new(
"belongs_to_user", false, "boolean"))
end # add a dummy column to the cloned class
end
In your controller use the following code:
#books = Book.extended_book.all(
:select => "books.*, IF(collections.id, 1, 0) AS belongs_to_user",
:joins => "LEFT OUTER JOIN collections
ON book.id = collections.book_id AND
collections.user_id = #{current_user.id}"
)
Now in your view you can do the following.
book.belongs_to_user?
Explanation:
In the extended_book method you are creating a copy of Book class and adding a dummy column belongs_to_user to the hash. During the query extra join column is not rejected as it exists in the columns_hash. You should use the extended_book only for querying.
If you use it for CRUD operations DB will throw error.
I would first create an instance method in the User model that 'caches' the all the Book ID's in his collection:
def book_ids
#book_ids ||= self.books.all(:select => "id").map(&:id)
end
This will only execute the SQL query once per controller request. Then create another instance method on the User model that takes a book_id as a parameter and checks to see if its included in his book collection.
def has_book?(book_id)
book_ids.include?(book_id)
end
Then while you iterate through the books:
<% if current_user.has_book?(book.id) %>
Only 2 SQL queries for that controller request :)
Use exists? on the association as it is direct SQL call. The association array is NOT loaded to perform these checks.
books.users.exists?(current_user)
This is the SQL executed by Rails.
SELECT `users`.id FROM `users`
INNER JOIN `collections` ON `users`.id = `collections`.user_id
WHERE (`users`.`id` = 2) AND ((`collections`.book_id = 1)) LIMIT 1
In the above SQL current_user id = 2 and book id is 1
current_user.books.exists?(book)
This is the SQL executed by Rails.
SELECT `books`.id FROM `books`
INNER JOIN `collections` ON `books`.id = `collections`.book_id
WHERE (`books`.`id` = 3) AND ((`collections`.user_id = 4)) LIMIT 1
In the above SQL current_user id = 4 and book id is 3
For more details, refer to the documentation of the exists? method in a :has_many association.
Edit: I have included additional information to validate my answer.