I have 2 models/tables:
class CollectionPoint < ActiveRecord::Base
belongs_to :collection_type
...
class CollectionType < ActiveRecord::Base
has_many :collection_points
...
Every CollectionPoint has a city (column)
Every CollectionType has a name (column)
I would like to get all the distinct collection type names in each city in order to show a directory like this in my view:
city A
collection_type.name A
collection_type.name B
collection_type.name C
...
city B
collection_type.name A
collection_type.name B
collection_type.name C
...
city C
collection_type.name A
collection_type.name B
collection_type.name C
...
...
The best thing would be an array that is grouped by cities so that i could go like this in my view:
#cities.each do |city|
...
city.each do |collection_type_name|
...
end
end
So i tried this:
CollectionPoint.select("DISTINCT city, collection_type_id")
But then i get only the collection type ids, not the collection type names. Also i don't need the collection point ids (which are nil in the query result anyway), so i tried this:
CollectionPoint.includes(:collection_type).select("DISTINCT city, collection_types.name").references(:collection_types)
But this is not getting me anywhere neither.
I'm quite frustrated because i know there must be a solution to this i don't have a clue about. Maybe you could help me?
Cheers
Solution A
The simplest solution to this problem was adding this line of code to my controller:
#cities = CollectionPoint.includes(:collection_type).select("DISTINCT city, collection_type_id").order(:city).group_by(&:city)
So i could show the results in my view accordingly:
- #cities.each do |city, collection_points|
%h2
= city
= content_tag :ul, title: "... " + city do
%li
= link_to "... " + city, "/.../" + city
- collection_points.each do |cp|
%li
= link_to cp.collection_type.name + " in " + city, "/" + cp.collection_type.name + "/" + city
Solution B
I guess the best solution is to create a separate City model and rework your assosiations:
class City < ActiveRecord::Base
has_many :collection_points
has_many :collection_types, through: :collection_points
...
class CollectionPoint < ActiveRecord::Base
belongs_to :collection_type
belongs_to :city
...
class CollectionType < ActiveRecord::Base
has_many :collection_points
has_many :cities, through: :collection_points
...
Then you can do all kinds of stuff, for example finding all collection types in a specific city:
City.where(name: "Cologne").first.collection_types
To list all distinct collection types in all distinct cities, the controller action looks like this:
#cities = City.includes(:collection_types).distinct
And in the view you can go like:
#cities.each do |city|
city.name
city.collection_types.each do |collection_type|
collection_type.name
end
end
More information on this can be found here:
Rails Model Assosiations
Thanks for your help, guys!
Do your CollectionName and CollectionType models have and belongs to many records? If so, this is the set up I would advise with:
Collector.rb
class Collector < ActiveRecord::Base
attr_accessible :collection_point_id, :collection_type_id
belongs_to :collection_point
belongs_to :collection_type
end
CollectionPoint.rb
class CollectionPoint < ActiveRecord::Base
attr_accessible :city
has_many :collectors
has_many :collection_types, :through => :collectors
end
CollectionType.rb
class CollectionType < ActiveRecord::Base
attr_accessible :name
has_many :collectors
has_many :collection_points, :through => :collectors
end
Then you can utilise the new ActiveRecord association and select distinct records with the group method:
#cities = CollectionPoint.select("DISTINCT(CITY)").all
Then print the #cities object with an each do method.
#cities.each do |city|
...
city.collection_types.each do |collection_type_name|
...
end
end
what about using active record -
grouped_by_city = CollectionPoint.all.group_by { |cp| cp.city }
would return
{ :city =>{stuff in here}}
and then you could do this:
- grouped_by_city.each do |city, info|
%p= city
%ul
%li= info.collection_type.name #not sure what this should be? depends on your models
if you play around with that a bit I think it will work for you. irb is your best friend!
Related
Let's say I have an Item model and Category model with has_many :through association:
class Item < ActiveRecord::Base
has_many :category_items
has_many :categories, through: category_items
end
class Category < ActiveRecord::Base
has_many :category_items
has_many :items, through: category_items
end
class CategoryItems < ActiveRecord::Base
belongs_to :category
belongs_to :items
end
now, I want to have a scope on items that will get all items that are in specific status (assume it has status attribute) for specific category. for example: get all items with status "in stock" and which belongs to category with id = 3, something like:
scope :in_stock_for_category, ->(category) { where(status: SOME_ENUMERATED_VALUE) ....
i'm missing the last part of the query to limit the result set to the specific category.
Thanks!
Since you don't have a category_id column in your items table, you need to join either category_items or cateogeries in your scope before you can specify a particular category's ID condition.
class Item < ActiveRecord::Base
scope :in_stock_for_category, -> do |category|
joins(:category_items).
where(category_items: {category_id: category.id}).
where(items: {status: SOME_ENUMERATED_VALUE}).
group("items.id") # grouping might be unnecessary since you're adding the where condition for the category's id
end
end
That will work. Or if you want to join categories, do the following:
class Item < ActiveRecord::Base
scope :in_stock_for_category, -> do |category|
joins(:categories).
where(categories: {id: category.id}).
where(items: {status: SOME_ENUMERATED_VALUE}).
group("items.id") # grouping might be unnecessary since you're adding the where condition for the category's id
end
end
If you already have a category however, it might be useful to create a has_many relationship for a items that have a certain status. Something like the following:
class Category < ActiveRecord::Base
has_many :in_stock_items, -> do
where(items: {status: SOME_ENUMERATED_VALUE})
end, through: :category_items, source: :item
end
Also, if you have a status scope in Item (something like scope :in_stock, -> { where(status: SOME_ENUMERATED_VALUE) }), you can most likely change the above has_many relationship to the following:
class Category < ActiveRecord::Base
has_many :in_stock_items, -> do
merge(Item.in_stock) # http://apidock.com/rails/ActiveRecord/SpawnMethods/merge
end, through: :category_items, source: :item
end
That should tidy things up.
I have a Product class that has_many Gender through Connection class instances. I want to query to find products that have both end_a and end_b present. The current class method works with 2 caveats:
Fails to return correctly if searching where end_a and end_b are the same. Instead should search if product has 2 instances, not just one of object.
Returns an Array when I want an ActiveRecord_Relation.
The class method .query is below, any feedback or ideas are appreciated.
class Product < ActiveRecord::Base
has_many :connections, dependent: :destroy, as: :connectionable
has_many :genders, through: :connections
def self.query(end_a, end_b)
search_base = active.joins(:connections)
end_a_search = search_base.where(connections: { gender_id: end_a } )
end_a_search & search_base.where(connections: { gender_id: end_b } )
end
end
ps: Once this is figured out will likely move this to a scope for Product
class Product < ActiveRecord::Base
has_many :connections, dependent: :destroy, as: :connectionable
has_many :genders, through: :connections
scope :with_genders, -> (end_a, end_b) {
relation = joins('INNER JOIN connections c1 ON c1.connectionable_id = products.id AND c1.connectionable_type = \'Product\'')
.joins('INNER JOIN connections c2 ON c1.connectionable_id = c2.connectionable_id AND c2.connectionable_type = \'Product\'')
.where(c1: {gender_id: end_a}, c2: {gender_id: end_b})
.group('products.id')
end_a == end_b ? relation.having('COUNT(products.id) > 1') : relation
}
end
I am trying to query for questions based on subject or category. I have a Category model which has many Subjects, and a Subjects model which has many Questions. How do I select 50 questions where the subject_id = x or category_id = y? I'm not sure if I need to change my model associations then query or use a query with the current associations. Here are the models (stripped of some excess code):
category.rb
class Category < ActiveRecord::Base
has_many :subjects, class_name: "Subject",
foreign_key: "category_id"
has_many :questions, through: :subjects
end
subject.rb
class Subject < ActiveRecord::Base
belongs_to :category
has_many :questions, class_name: "Question",
foreign_key: "subject_id"
end
question.rb
class Question < ActiveRecord::Base
belongs_to :subject
end
The most success I've had is with "Question.joins(:subject).group(category_id:1)", which only returns the last question with an associated category. Any suggestions? Thanks!
So you could make a scope
class Question < ActiveRecord::Base
scope :q_or_c ->(q, c){ where('category_id = ? OR question_id = ?', q, c) }
...
end
and then call it with
Question.q_or_c(question_id, category_id)
I have a fully working (for some time now) many-to-many relationship in my Rails application.
Instructors has many Schools (through SchoolsToInstructorsAssociations)
Schools has many Instructors (through SchoolsToInstructorsAssociations)
At this time, I would like the ability to have an "active state" in addition to simply adding or removing an Instructor from a School or a School from an Instructor.
I want an Instructor to be set as inactive before being removed completely at a later point (or reactivated).
My first thought was to add an 'active' boolean to the relationship model (SchoolsToInstructorsAssociations), but there's no simple way to access this attribute to update or query it).
My second thought was to simply create another relationship model with the 'active' attribute, but it's redundant and something extra I have to track.
Maybe a custom many-to-many module? Create a SchoolsToInstructorsAssociations controller?
class Instructor < ActiveRecord::Base
has_many :schools_to_instructors_association
has_many :schools, :through => :schools_to_instructors_association
end
class School < ActiveRecord::Base
has_many :schools_to_instructors_association
has_many :instructors, :through => :schools_to_instructors_association
end
class SchoolsToInstructorsAssociation < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
I also plan to create a history record each time an instructors 'active' state changes or an instructor is removed or added to a school. Not asking how to do this, but wondering if it could be used to track an instructors 'active' state.
class SchoolsController < ApplicationController
def instructors_index
#school = School.find(params[:id])
instructors = find_instructors
#active_instructors = instructors[0]
#inactive_instructors = instructors[1]
respond_to do |format|
format.html # index.html.erb
format.json { render json: #schools }
end
end
private
def find_instructors
active = []; inactive = []
#school.instructors.each do |s|
if SchoolsToInstructorsAssociationRecord.where(user_id: s, school_id: #school)[0].active?
active << s
else
inactive << s
end
return [active, inactive]
end
end
end
class SchoolsToInstructorsAssociationRecord < ActiveRecord::Base
default_scope order('created_at DESC')
attr_accessor :user_id, :school_id, schools_to_instructors_association_id, :active
end
Sounds like you can accomplish what you're trying to do with scopes. Add a boolean column for 'active' as you described for the 'Instructor' class, then you can add scopes for it:
class Instructor < ActiveRecord::Base
...
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
...
end
Then for a given School, you can get the active (or inactive) instructors for that school:
#school.instructors.active
=> SELECT "instructors".* FROM "instructors" WHERE "instructors"."school_id" = <id> AND "instructors"."active" = 't'
If you wanted to do some operations on all the inactive instructors (like destroy them, as an example), you could do:
Instructor.inactive.map(&:destroy)
And you can of course write whatever custom methods you want for the Instructor or School classes.
I have a model, Category. And I want to create an new default sub_category when ever the category is created. But I'm not sure how to do it. Here is what I have.
class Category < ActiveRecord::Base
attr_accessible :title, :position
has_many :sub_categories
after_create :make_default_sub
def make_default_sub
#Sub_Categories.new( :title=>' ');
end
end
Why not to use ancestry gem? In the future if you will have more subcategories, it will be easier to manage them.
For example in your case:
class Category < ActiveRecord::Base
attr_accessible :title, :position
has_ancestry
after_create :create_default_subcategory
def make_default_sub
children = self.children.new
children.title = ''
children.position = 1 # or autogenerated
children.save!
end
end
But can you explain, why do you need such a strange default behaviour?
Thanks