I have recently been taking a course in Rails. We are tasked with creating three named scopes in our 'Product' model. I have done:
scope :books, where( :category => 'books')
scope :movies, where( :category => 'movies')
scope :music, where( :category => 'music')
When I call these as 'Product.books' or 'Product.movies' from the command line, I am expecting to see a return of all of my products that are books, or movies. All I get is an empty array []. Is the problem in the definition of the scopes (which I assume), or how I am trying to access them?
Your syntax is OK. I tried it by adding some books and movies.It worked fine and displayed books when i run Product.books.
So,your problem is empty database which is resulting in empty array.
Is Category its own model that is related to Product (such as by has_many or has_one)?
If this is the case, you will need to do a joins with the Category
For example, see the following code:
class Product < ActiveRecord::Base
has_one :category
scope :books, joins(:category).where('categories.name' => 'book')
end
class Category < ActiveRecord::Base
belongs_to :product
end
In this simple example, both Product and Category only have name attributes as strings (in the migrations), and Category also has product_id.
Your syntax is correct. But first check your database, may be you don't have data related to the Product.books or Product.movies.
Related
I have the following models
class Order < ApplicationRecord
has_many :order_details
has_many :products, through: :order_details
end
class OrderDetail < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :order_details
has_many :orders, through: :order_details
end
And I already have product records in my database.
Now, if using syntax: Order.create name: 'HH', product_ids: [1,2]
1 Order record is created, and rails automatically creates 2 more OrderDetail records to connect that Order record with 2 Products.
This syntax is quite handy.
Now, I want to learn more about it from the Rails documentation. But now i still can't find the documentation about it. Can someone help me find documents to learn more?
[Edit] Additional: I'd like to find documentation on the Rails syntax that allows passing a list of ids to automatically create records in the intermediate table, like the Order.create syntax with ```product_ids` `` that I gave above.
The extensive documentation is at https://api.rubyonrails.org/, and many-to-many is here.
The essential part is to analyze the source code of Rails at Module (ActiveRecord::Associations::CollectionAssociation) and at id_writers method:
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
primary_key = reflection.association_primary_key
pk_type = klass.type_for_attribute(primary_key)
ids = Array(ids).compact_blank
ids.map! { |i| pk_type.cast(i) }
# .... code continues
We see that ids parameter (ex.: [1,2]) is first checked to be Array then the compact_blank method removes all falses values, after that, ids are casted to match primary_key type of the model (usually :id). Then code continues to query database with where to get found ids (associations) and saves.
I am trying to use a default scope to impose a sort order on the model QuizCategoryWeight. The goal is to get #possible_answer.quiz_category_weights to return the weights in sorted order.
Update: I have narrowed the problem down to the fact that default scopes seem to work for me as long as they just have an 'order' method but not when the 'includes' method is chained with the 'order' method. However, this chaining does work for named scopes.
Could it be my development environment? Or is this a bug in Rails perhaps?
I am using windows, so maybe that's the problem. Currently on ruby 2.0.0p645 (2015-04-13) [i386-mingw32] and Rails 4.2.4...
The following, using a default scope on QuizCategoryWeight, does not seem to work:
class QuizCategoryWeight < ActiveRecord::Base
#trying to use a default scope, but does not work
default_scope { includes(:quiz_category).order("quiz_categories.sort_order") }
belongs_to :possible_answer, inverse_of: :quiz_category_weights,
class_name: 'QuizPossibleAnswer', foreign_key: 'possible_answer_id'
belongs_to :quiz_category
end
class QuizPossibleAnswer < PossibleAnswer
has_many :quiz_category_weights,
#does not work whether the line below is used or not
->{ includes(:quiz_category).order("quiz_categories.sort_order") },
inverse_of: :possible_answer,
dependent: :destroy,
foreign_key: 'possible_answer_id'
end
class QuizCategory < ActiveRecord::Base
default_scope { order :sort_order }
end
With a named scope, it does work. However, this means that I have to add an argument to my form builder to use the collection 'f.object.quiz_category_weights.sorted'.
class QuizCategoryWeight < ActiveRecord::Base
# named scope works...
scope :sorted, ->{ includes(:quiz_category).order("quiz_categories.sort_order") }
belongs_to :possible_answer, inverse_of: :quiz_category_weights,
class_name: 'QuizPossibleAnswer', foreign_key: 'possible_answer_id'
belongs_to :quiz_category
end
class QuizPossibleAnswer < PossibleAnswer
has_many :quiz_category_weights,
inverse_of: :possible_answer,
dependent: :destroy,
foreign_key: 'possible_answer_id'
end
I think there is a bug with using 'includes' with a default scope, either in the Rails framework generally or in my windows version.
However, I've found that using 'joins' does work. I'm not using any of other the attributes from QuizCategory so it's more appropriate to my use case as well: I only want to sort using the 'sort_order' attribute from the joined table.
The fixed code is:
class QuizCategoryWeight < ActiveRecord::Base
default_scope { joins(:quiz_category).order("quiz_categories.sort_order") }
belongs_to :quiz_category
end
The includes method was introduces for relations to give Rails a hint to reduce database queries. It says: When you fetch Objects of type A, also fetch associated objects, because I need them later, and they should not fetched one by one (the N+1 queries problem)
The includes was first implemented with two database queries. First all A, then all B with one of the ids from A. Now includes often uses a sql join to have only one database query. But this is an internal optimisation.
The concept is object oriented, you want objects from A, then you retrieve the B through the A. So I think, if you set the order from the included B back to A, you are doing more than was meant for the original includes.
TL;DR: How do I use the ID of the respective parent object in a has_many SQL clause to find child objects?
Long version:
I have the following example code:
class Person < AR::Base
has_many :purchases, -> {
"SELECT * from purchases
WHERE purchase.seller_id = #{id}
OR purchase.buyer_id = #{id}"
}
This was migrated from Rails 3 which worked and looked like
has_many :purchases, :finder_sql => proc { #same SQL as above# }
I want to find all purchases associated with a Person object in one association, no matter whether the person was the one selling the object or buying it.
Update: I corrected the SQL, it was inside out. Sorry! Also: The association only needs to be read-only: I am never going to create records using this association, so using id twice should be OK. But I do want to be able to chain other scopes on it, e.g. #person.purchases.paid.last_5, so creating the sum of two associations in a method does not work (at least it didn't in Rails 3) since it doesn't return an AM::Relation but a simple Array.
When using this above definition in Rails 4.2, I get
> Person.first.purchases
undefined method `id' for #<Person::ActiveRecord_Relation:0x...>
The error is clear, but then how do I solve the problem?
Since this is only an example for much more complicated SQL code being used to express has_many relationships (e.g. multiple JOINS with subselects for performance), the question is:
How do I use the ID of the parent object in a has_many SQL clause?
I don't think your code will work at all. You are defining an association with two foreign keys ... that'd mean that in case you want to create a new Person from a present Purchase, what foreign key is to be used, seller_id or buyer_id? That just don't make sense.
In any case, the error you are getting is clear: you are calling a variable id which is not initialized in the block context of the SQL code.
A better approach to the problem I understand from your question would be to use associations in the following way, and then define a method that gives you all the persons, both buyers and sellers that a product has. Something like this:
class Purchase < ActiveRecord::Base
belongs_to :buyer, class_name: 'Person'
belongs_to :seller, class_name: 'Person'
def persons
ids = (buyer_ids + seller_ids).uniq
Person.where(ids: id)
end
end
class Person < ActiveRecord::Base
has_many :sold_purchases, class_name: 'Purchase', foreign_key: 'buyer_id'
has_many :buyed_purchases, class_name: 'Purchase', foreign_key: 'seller_id'
end
Im my approach, buyer_id and seller_id are purchase's attributes, not person's.
I may have not understood correctly, in that case please clarify.
High guys. This isn't behaving the way I think it should which means I'm doing it wrong;
class Tag < ActiveRecord::Base
has_and_belongs_to_many :properties
end
class Property < ActiveRecord::Base
has_and_belongs_to_many :tags
def amenities
tags.where(:classification => :amenity)
end
end
So I have Properties and Tags. They have a HABTM relationship with a pivot table.
When I do a .tags on a property, I get the full list and if I do a .clear on that full list it correctly removes the associations from the database.
When I do a .amenities I get only those tags that are flagged with the classification of amenity correctly, but if I do a .clear on those results it fails to remove them but rather just does the .amenities query again in the console with an output of [].
So this means it's just .clear'ing the result array.. not the association which is what I actually want.
So the question then is; what is the correct way to .clear an association from a HABTM relationship while giving it essentially a where clause to limit which associations are being removed?
Thanks guys. Hope that wasn't too confusing..
Instead of defining a method querying tags, you could add another tag association with conditions, like:
class Property < ActiveRecord::Base
has_and_belongs_to_many :tags
# this will be just like the tags association, except narrow the results
# to only tags with the classification of 'amenity'
has_and_belongs_to_many :amenities,
:class_name => 'Tag',
:conditions => { :classification => 'amenity' }
end
clear, and any other habtm assocation methods, should work as expected.
I have 2 models:
class Video < ActiveRecord::Base
belongs_to :categories, :foreign_key => "category", :class_name => "Category"
end
class Category < ActiveRecord::Base
has_many :videos
end
This is fine so far, in my videos controller for the index page I have:
def index
#videos = Video.all(:joins => :categories)
etc, etc
end
The above produces the following SQL Query: SELECT videos.* FROM videos INNER JOIN categories ON categories.id = videos.category
Which is fine up to a certain point, basically I need to get the category name (a field in that table) that way I don't have to do another call in the view to get category name based on the category id. Any ideas?
Thank you, and yes I am new to ruby, I tried reading the API but couldn't find much help there.
If think the association in your Video class is set up incorrectly. It should be:
class Video < ActiveRecord::Base
belongs_to :category
end
You can do #videos = Video.all(:include => :category). This will retrieve the video and associated category records using a single SQL statement.
Incidentally, your :class_name option on the belongs_to association is redundant because ActiveRecord will already have automatically inferred the class name from the association name. You should only use this option if you want to have an association name that is different from the underlying class e.g. authors/Person.
join_condition = " ,categories where INNER JOIN categories ON categories.id=videos.category"
#videos = Video.all(:joins => join_condition)
try this