get name of object following 2 time a relationship (2x has_many) - ruby-on-rails

I have an author, who writes products, which are placed in groups.
What I want to do is to list the groups that an author is involved into.
So, I have first to follow the link from author to products, which works with :
#author = Author.find(params[:id])
#products = #author.products
which gives lot of products :-) Then I have to find (all) the groups that are linked to all of the products (because a group can contain a lot of products, I have already a has_many link with group_id column in the product table)
But when I try to write something as
#groups = #author.products.groups
I get the error : undefined method `groups' for # Class:0x000000032a2198
Why ?
Here is the model, where only the "through" clauses seem not to work ?
class Author < ActiveRecord::Base
has_and_belongs_to_many :products
has_many :groups, :through => :products
end
class Product < ActiveRecord::Base
belongs_to :group
has_and_belongs_to_many :authors
end
class Group < ActiveRecord::Base
has_many :products, :dependent => :destroy
has_many :authors, :through => :products
end
Thanks !
Edit : I found an ugly way to do what I want. But is there no better RoR way ??
def show
#author = Author.find(params[:id])
#products = #author.products
#groups = []
#products.each do |product|
group = Group.find(product.group_id)
unless #groups.include?(group)
#groups << group
end
end
end

As far as I know there's no way to do it in a Rails way. First, here's how to make what you have more "Railsy":
def show
#author = Author.find(params[:id])
#products = #author.products
#groups = []
#products.each do |product|
unless #groups.include?(product.group)
#groups << group
end
end
end
But a shorter way would be:
#products.map(&:group).uniq
Quick explanation of what it's doing. First it's using the symbol to proc shorthand that was introduced in Rails 1.1 (http://blog.hasmanythrough.com/2006/3/7/symbol-to-proc-shorthand). That builds an array of groups. Then b/c, in your question, you are checking for uniqueness (using include?) I call .uniq which weeds out any duplicates.

I also found another way to do this :
#user = User.find(params[:id])
#groupcollect = []
#user.products.each { |e| #groupcollect << e.group_id} # collect group_id for all products
#mygroups = Group.find(#groupcollect.uniq) # gets the groups for all group_id, unique
:-)

Related

Get list of all belongs_to models in polymorphic association

I have these two models:
class Review < ActiveRecord::Base
belongs_to :reviewable, polymorphic: true
end
class Article < ActiveRecord::Base
has_one :review, as: :reviewable, dependent: :destroy
end
Review model holds one key which helps me to get all the Reviews which belong to some user.
I can get all those Reviews like this:
#reviews = Review.where("user_id = ?", current_user.id)
For one review I would just do this:
#review = Review.where("user_id = ?", current_user.id).first
#article = #review.article
But how can I now from a list of #reviews get a list of all articles which are connected to those reviews?
If the #article = #review.article code works then your just asking how to iterate over a collection of reviews? In that case
#reviews = Review.where("user_id = ?", current_user.id)
# in your view
#reviews.each do |review|
review.article
end
You could collect:
articles = #reviews.collect {|review| review.article}
If you mean list all of related models with your any model. Your answer is here clearly;
https://stackoverflow.com/a/12784574/3425913
Thing.reflections.collect{|a, b| b.class_name if b.macro==:belongs_to}.compact
You can call :has_many or :has_one associations too
You could eager load the associations
#reviews = Review.where("user_id = ?", current_user.id).includes(:reviewable)
and if you really need all of the the reviewables, you can:-
#reviewables = #reviews.map(&:reviewable).uniq

Trying to insert an array into a column

I want to add every cart item into a column (text box) but i can't seem to use "<<" which is a bitwise operator. Is there a way to add every cart item for the order so all the items will appear in the "products" column.
class OrdersController < ApplicationController
before_filter :extract_shopping_cart, :only => [:new, :show]
def new
#order = Order.new
#order.total = #shopping_cart.total
#order.sub_total = #shopping_cart.subtotal
#order.sales_tax = #shopping_cart.taxes
#shopping_cart.shopping_cart_items.each do |cart|
#order.products << cart.item
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #order }
end
end
def extract_shopping_cart
shopping_cart_id = session[:shopping_cart_id]
#shopping_cart = session[:shopping_cart_id] ? ShoppingCart.find(shopping_cart_id) : ShoppingCart.create
session[:shopping_cart_id] = #shopping_cart.id
end
EDIT: #shopping_cart is from the gem acts_as_shopping_cart
My Order model is as follows
class Order < ActiveRecord::Base
# attr_accessible :title, :body
attr_accessible :total, :sub_total, :sales_tax, :products
belongs_to :user
end
I have an error:
undefined method `<<' for nil:NilClass
Sample code: https://github.com/atbyrd/Bootstrapped_Devise
Check the relationship between Order and Product. The way you're writing it, it looks like Order has_many products and Product can belong to many Orders. I don't see that reflected in your Order model.
In other words, you would have to use has_and_belongs_to_many, like:
class Order < ActiveRecord::Base
attr_accessible :total, :sub_total, :sales_tax, :products
has_and_belongs_to_many :products
belongs_to :user
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :orders
end
The guide does a great job of describing how this works with charts and everything.
Instead of making products a column, it's probably better to add a has_and_belongs_to_many relationship between orders and products so that Rails will have by default an
order.products
array and also a
product.orders
array, where "order" and "product" refer to an instantiated Order or Product with a unique id. This also makes filtering by order or product easy because say you want all the products for a specific order, you could do so by doing something like
#products = Order.find(session[:some_id]).products
to find all products for the order with the specified id, and you could do a similar thing for the products.orders array. Hopefully this helps.

rails adding tags to controller

I am implementing a basic tagging feature to my app. New to rails.
I have a listings model and I am working in the listings_controller # index. When the user goes to the listing page and sets the :tag param I want #users only to hold the users which match the tag.
So if they goto www.example.com/listings?tag=foo only pages which have been tagged with foo are loaded. This is what I have come up with so far but there is several problems with it.
def index
if params[:tag]
#id = Tag.where(:name => params[:tag]).first.id
#listingid = Tagging.where(:tag_id => #id)
#listingid.each do |l|
#users = User.find(l.listing_id)
end
else
#users = User.all
end
end
I am not sure how to loop and add each found user to #users. I think I may be going about this whole thing the wrong way.. My tag/tagging models look as follows:
tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :listings, through: :taggings
end
tagging.rb
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :listing
end
Taggings has the following colums:
id, tag_id, listing_id
Tags has the following columns:
id, name
Any guidance would be appreciated, been trying to fix this for a while with no luck.
Trying with
def index
#tag = Tag.where(:name => params[:tag]).first
if #tag
#listings = #tag.listings.includes(:user)
#users = #listings.map{|l| l.user}
else
#users = User.all
end
end

Rails - how to join/group nested tables and get joined values out?

I have the following hierarchy of models where each one has_many of the one below it:
class AccountGroup < ActiveRecord::Base
has_many :accounts, :inverse_of=>:account_group
# name: string
class Account < ActiveRecord::Base
belongs_to :accountGroup, :inverse_of=>:account
has_many :positions, :inverse_of=>:account
class Position < ActiveRecord::Base
belongs_to :account, :inverse_of=>:positions
# net_position: integer
In other words, an AccountGroup contains a bunch of Accounts, and an Account contains a bunch of Positions.
Goal: I want an hash of AccountGroup => (sum of its net_positions). That means there's a GROUP BY involved.
I can do this with raw SQL, but I haven't cracked it with Rails functions. The raw SQL is:
SELECT account_groups.id,SUM(net_position),account_groups.name
FROM account_groups
LEFT JOIN accounts ON accounts.account_group_id = account_groups.id
LEFT JOIN positions ON positions.account_id = accounts.id
GROUP BY account_groups.id,account_groups.name;
Is this something that Rails just can't do?
Rails (4.0.0) can do this - we have two ways to do it currently:
1. SQL "Alias" Columns
Rails Scoping For has_many :through To Access Extra Data
#Images
has_many :image_messages, :class_name => 'ImageMessage'
has_many :images, -> { select("#{Image.table_name}.*, #{ImageMessage.table_name}.caption AS caption") }, :class_name => 'Image', :through => :image_messages, dependent: :destroy
2. ActiveRecord Association Extensions
This is a little-known feature of Rails, which allows you to play with the collection object. The way it does it is to extend the has_many relationship you have created:
class AccountGroup < ActiveRecord::Base
has_many :accounts do
def X
#your code here
end
end
end
We have only got this method working for collections, but you can do all sorts with it. You should look at this tutorial to see more about it
Update
We just got this working by using an extension module:
#app/models/message.rb
Class Message < ActiveRecord::Base
has_many :image_messages #-> join model
has_many :images, through: :image_messages, extend: ImageCaption
end
#app/models/concerns/image_caption.rb
module ImageCaption
#Load
def load
captions.each do |caption|
proxy_association.target << caption
end
end
#Private
private
#Captions
def captions
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({caption: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:caption)
end
#Target
def target_collection
#load_target
proxy_association.target
end
end
Props to this gist for the variable functions
This basically overrides the load ActiveRecord function in the CollectionProxy class, and uses it to create our own proxy_association.target array :)
If you need any information on how to implement, just ask in the comments
You can make this little bit more prettier than raw sql by using rails AR querying methods:
AccountGroup.
select("account_groups.id, SUM(net_position), account_groups.name").
joins("LEFT JOIN accounts ON accounts.account_group_id = account_groups.id").
joins("LEFT JOIN positions ON positions.account_id = accounts.id").
group("account_groups.id,account_groups.name")
This can be done with pure Arel as well.
AccountGroup.select(
AccountGroup.arel_table[:id], Arel::Nodes::NamedFunction.new('SUM', [:net_position]), AccountGroup.arel_table[:name]
).joins(
AccountGroup.arel_table.join(Account.arel_table).on(
Account.arel_table[:account_group_id].eq(AccountGroup.arel_table[:id])
).join_sources
).joins(
AccountGroup.arel_table.join(Position.arel_table).on(
Position.arel_table[:account_id].eq(Account.arel_table[:id])
).join_sources
).group(
AccountGroup.arel_table[:id], AccountGroup.arel_table[:name]
)
I'm not 100% sure this will work, I simply copied your SQL from above and put it into scuttle.io
Use include function, in example
ac = AccountGroup.all(:include => :account)
$ AccountGroup Load (0.6ms) SELECT `account_groups`.* FROM `groups`
$ Account Load (16.4ms) SELECT `accounts`.* FROM `accounts` WHERE `accounts`.`id` IN (1010, 3, 4, 202, 203, 204, 9999)
Then you can call ac.account.name or something like that
There are a great Railscast http://railscasts.com/episodes/22-eager-loading?view=asciicast
If you really want to use ActiveRecord for this (no SQL), it will be something like:
ags = AccountGroup.all(:include => {:accounts => :positions})
hash = Hash[ags.map { |ag| [ag, ag.map(&:accounts).flatten.map(&:positions).flatten.map(&:net_position).reduce(0,&:+)]}]
But it will be slower than your SQL, and not any prettier.
Is this something that Rails just can't do?
As this question has been open for about a month, I'm gonna to go ahead and assume the answer to this question is...
Yes.
EDIT: Yes, for Rails 3. But Rails 4 can do it! See accepted answer.
Rails can't do it, outside of using find_by_sql or ActiveRecord::Base.connection.execute(query), which are pretty kludgy and not rails-y.

What is a good way to deal with categories in a rails application?

I am trying to get my head around how to deal with my Products <-> Categories relation.
I am trying to build a small shop in rails and I want to make a navigation out of the category tree.
The navigation will look something like this:
- Men
|--Shirts
|--Pants
- Woman
|--Shirts
|--Dresses
-Accessoires
You get the idea...
Now, the problem is that these appear to be all different scopes on the same model, Product, with different find conditions on the associated Category.
My models so far:
class Product < ActiveRecord::Base
# validations...
has_many :categorizations
has_many :categories, :through => :categorizations
# more stuff ...
end
class Category < ActiveRecord::Base
acts_as_nested_set
has_many :categorizations
has_many :products, :through => :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
Also, I want to have multiple categories on my products and maybe make it possible to create new categories "on-the-fly" when adding a product. So the whole category management should be as easy as possible. If someone can point me in the right direction or link me to a tutorial, best practice or anything would be really awesome!
UPDATE
Ok, so now I can creating categories on the fly using virtual attributes, the question is how do I search for articles of a specific category?
What I tried:
#products = Product.scoped(:include => :categorizations, :conditions => {:category_names => params[:category]})
or
#products = Product.where("categorization = ?", params[:category])
but both didnt work. basically i want all products of one category...
You can allow users to create new categories at the same time as creating new products by using accepts_nested_attributes_for in your model. Have a look through the documentation for that to get you started.
So I ended up creating a many-to-many relation through categorizations. This railscast explains perfectly how to do this and create new categories (or tags) on-the-fly.
After I loop through the categories to make them links in my product overview:
# app/views/products/index.html.erb
<ul class="categories">
<% for category in #categories %>
<li><%= link_to category.name, :action => "index" , :category => category.id %></li>
<% end %>
</ul>
and then in the controller I build the products from the category if there is any:
# products_controller.rb
def index
if params[:category]
#products = Category.find(params[:category]).products
else
#products = Product.scoped
end
#products = #products.where("title like ?", "%" + params[:title] + "%") if params[:title]
#products = #products.order('title').page(params[:page]).per( params[:per_page] ? params[:per_page] : 25)
#categories = Category.all
end
for sure there is a more elegant way to do it but this wors for now.. any improvement appreciated.

Resources