Say if #news_writers is an array of records. I then want to use #news_writers to find all news items that are written by all the news writers contained in #news_writers.
So I want something like this (but this is syntactically incorrect):
#news = News.find_all_by_role_id(#news_writers.id)
Note that
class Role < ActiveRecord::Base
has_many :news
end
and
class News < ActiveRecord::Base
belongs_to :role
end
Like ennen, I'm unsure what relationships your models are supposed to have. But in general, you can find all models with a column value from a given set like this:
News.all(:conditions => {:role_id => #news_writers.map(&:id)})
This will create a SQL query with a where condition like:
WHERE role_id IN (1, 10, 13, ...)
where the integers are the ids of the #news_writers.
I'm not sure if I understand you - #news_writers is a collection of Role models? If that assumption is correct, your association appears to be backwards - if these represent authors of news items, shouldn't News belong_to Role (being the author)?
At any rate, I would assume the most direct approach would be to use an iterator over #news_writers, calling on the association for each news_writer (like news_writer.news) in turn and pushing it into a separate variable.
Edit: Daniel Lucraft's suggestion is a much more elegant solution than the above.
Related
as title said i am trying to access an array of objects of an association
This is a has_many association
here is my class
class Keyword < ApplicationRecord
has_many :rankings
end
class Ranking < ApplicationRercord
belongs_to :keyword
end
There are a attribute in ranking called position:integer, i want to be able to access all latest created rankings from all keyword here is what i got so far
Keyword.all.joins(:rankings).select( 'MAX(rankings.id) ').pluck(:created_at, :keyword_id, :position)
i've read some other post suggesting me to use MAX on rankings.id, but i am still not able to return the array
At the moment Keyword.count return 4597
Ranking.count return 9245
Each keyword has generated about 2 rankings, but i just want the latest ranking from each keyword in array format, so to get latest of each i should expect around 4597
Not sure if i explained clear enough, hope u guys can help me :'( thanks really appreciate it
If you are using Postgres. You can use DISTINCT ON
Keyword.joins(:rankings)
.select("DISTINCT ON(ratings.keyword_id) keywords.*, ratings.position, ratings.created_at AS rating_created_at")
.order("ratings.keyword_id, ratings.id DESC")
Now you can access position, rating_created_at
#keywords.each do |k|
k.position
....
#keywords.map { |k| [k.id, k.rating_created_at, k.position] }
If you have enough rankings you might want to store the latest ranking on the on keywords table as a read optimization:
class Keyword < ApplicationRecord
belongs_to :latest_ranking, class_name: :ranking
has_many :rankings, after_add: :set_latest_ranking
def set_latest_ranking(ranking)
self.update!(latest_ranking: ranking)
end
end
Keyword.joins(:latest_ranking)
.pluck(:created_at, :id, "rankings.position")
This makes it both very easy to join and highly performant. I learned this after dealing with an application that had a huge row count and trying every possible solution like lateral joins to improve the pretty dismal performance of the query.
The cost is an extra write query when creating the record.
Keyword.joins(:rankings).group("keywords.id").pluck("keywords.id", "MAX(rankings.id)")
This will give you an array which elements will include an ID of a keyword and an ID of the latest ranking, associated with that keyword.
If you need to fetch more information about rankings rather than id, you can do it like this:
last_rankings_ids_scope = Ranking.joins(:keyword).group("keywords.id").select("MAX(rankings.id)")
Ranking.where(id: last_rankings_ids_scope).pluck(:created_at, :keyword_id, :position)
Consider this setup. Please understand that our setup is much more detailed but this is a simple example.
competition which has name. This is an annual competition.
competition_instances which has location, starts_at.
Each competition has sports which has name.
Example:
competition.name: "Super Bowl" has different competition_instances every year but sport remains the same.
Conversely, competition.name: "Olympics" has different competition_instances and different sports in each competition_instance.
Would it be best to create competition_sports and competition_instance_sports with competition_instance_sports as a subclass of competition_sports?
GOAL: Use competition_instance_sports records if they exist, otherwise use the competition_sports record. In our real world app, each competition/competition_instance can have 20-50 sport records. How can we best achieve?
Based on what I understand from the question I cannot see where STI will be helpful in this situation. However a join table will get you where you want.
I suggest creating a new table sports, this model will have all the specific details of each sport. The competition_instance.rb will have one/many sport.rb. competiton.rb will have many sports through competition_instance.rb.
competition.rb
Class Competition < ActiveRecord::Base
has_many :competition_instances
has_many :sports, through: :competition_instances
end
competition_instance.rb
Class CompetitionInstance < ActiveRecord::Base
belongs_to :competition
belongs_to :sport
end
sport.rb
Class Sport < ActiveRecord::Base
has_many :competition_instances
end
By using this design you will be able to achieve the following:
1- You will have your predefined sports in your database along with their specific properties.
2- Each competition will have .sports which will give all the sports in this competition for the olympics case.
3- You will be able to set specific properties for each competition instance (example event_start_time and event_end_time) in the competition instance table.
I'm just thinking of the case where there are standard sports which are always in the Olympics and some which are added on, such as those proposed by the host country.
I would use Polymorphic Associations, in a "reverse manner".
class Competition < ActiveRecord::Base
has_many :competition_instances
has_many :competition_sports, as: :event
end
class CompetitionInstance < ActiveRecord::Base
belongs_to :competition
has_many :competition_sports, as: :event
def events_array # neater by sacrificing ActiveRecord methods
competition.competition_sports + competition_sports
end
def events # messier, returns ActiveRecord relationship
CompetitionSport.where( " ( event_id = ? AND event_type = 'Competition' ) OR
( event_id = ? AND event_type = 'CompetitionInstance')", competition_id, id )
end
end
class Sport < ActiveRecord::Base
has_many :events, as: :competition_sport
end
class CompetitionSport < ActiveRecord::Base
belongs_to :sport
belongs_to :event, polymorphic: true
end
This allows:
competition.competition_sports # standard sports
competition_instance.competition_sports # only those specific for this instance
competition_instance.events # includes sports from both
Aiming at the original question of "Is this a use case for STI", it will be difficult to say if this is or not without seeing the full complexity of your environment. But here's are some things to consider:
Abridged from How (and When) to Use Single Table Inheritance in Rails - eugenius blog:
STI should be considered when dealing with model classes that share much of the same functionality and data fields, but you want more granular control over extending or adding to each class individually. Rather than duplicate code or forego the individual functionalities, STI permits you to use keep your data in a single table while writing specialized functionality.
You've simplified your example, but it sounds like competition and competition_instance are NOT essentially the same object, with minor behavioral differences. With what you've described, I would probably rename these objects to event and competition, or whatever makes better sense to you in terms of illustrating what these objects are actually representing. I think of 'The Olympics' as a generic event, and '100m Dash' as a competition taking place at The Olympics. Or "The SuperBowl" only hosted one competition in 2014, "SuperBowl XLIX".
You've also tagged this question with database-normalization, which STI won't help. If the attributes differ slightly between your shared objects you'll end up with null fields everywhere.
I refer you to the other answers here to see how you should probably set those objects up to behave as desired.
This is not a case for single table inheritance, because competition_instance is not a substitute for competition and 1 competition can have many competition_instances. So you have 3 tables:
competitions
sports
competition_instances
competition_instances has a foreign key to competitions because 1 competition can have many competition_instances but each competition_instance has exactly one competition.
Whether you attach sports to competitions or competition_instances depends on the specific constraints of your use case. I don't exactly know what you mean by "each competition/competition_instance can have 20-50 sport records". I would expect each competition_instance to have exactly one sport, so you might leave it at that, or you might attach a collection of sports to a competition as well, so that you can retrieve new competitions by sport before there is a competition_instance. I'd need more details on your use case to give you further advice.
I want my route to be something like cars(/:country/):car_id, what is the best way to do that? Only "cars" will list all the cars and "cars /: country" will list all the cars that are made in that country.
Now I have my route like this resources: cars,: path => "cars (/:country)" and I check in cars#index action if params[:country] is nil to determine what will be retrieved from the database .
My solution feels wrong and ugly and I guess the best solution and cleanest would be to make a country model, but do not really know how to organize it all up, tips?
country must have a slug and so do car_id too (using friendly_id for car_id). It feels like I should have a car table with name and slug thats all i have figured out.
Thanks!
First I'd say that your current solution is NOT ugly, nor wrong, at worst it's pedestrian. But without seeing all the involved models and associations, I can only give a general answer.
First, A country model, probably a good idea, but how do you relate it to the cars model?
You could do this:
class Country << ActiveRecord::Base
has_may :cars
end
class Car << ActiveRecord::Base
belongs_to :country
end
That would support semantics where by you could select a country, and get all cars belonging to a certain country, i.e.
#cars = Country.find('USA').cars
OR, you could do something like:
class Car << ActiveRecord::Base
has_one :country
end
class Country << ActiveRecord::Base
end
That would enable a different semantic:
#country = Car.find('Jeep').country
The point is to think of the query semantics you'd like to have in your app, and then define your associations to support the semantics that make sense for your app. I've posted very simple associations, you may end up with multiple and more complex associations, just depends on how you need to query the database and the associated models.
UPDATE
You posted:
I want my route to be something like cars(/:country/):car_id,
That doesn't make sense, if you know the specific car_id, you don't need any filtering or extra searching.
Sound like you want these URLs:
/cars # all cars
/cars/:country # all cars in country
/car/:id # a specific car
The first and third routes are probably there assuming you've defined the full set of RESTful routes for cars, i.e.
config/routes.rb
resources :cars
You just need to add to routes.rb:
GET '/cars/:country' => 'cars#index'
Then in app/controllers/cars_controller.rb:
def index
if params[:country]
#cars = Car.where("country_id = ?", params[:country])
else
#cars = Car.all
end
end
This assumes you have a relationship set up whereby each car record has a country_id attribute. That can come about in several ways, for example:
class Car < ActiveRecord::Base
belongs_to :country
end
That says my car table has a country_id attribute, and I can do something like:
#car = Car.find(1001)
"The car is in #{#car.country.name}"
I've just created two models and one "join table". Person, Adress (create_adresses_personss)
class Person < ActiveRecord::Base
has_and_belongs_to_many :streets
end
class Street < ActiveRecord::Base
has_and_belongs_to_many :persons
end
Now I want to add some data to these models in the db/seeds.rb file. The tutorial I follow just adds the objects:
person = Person.create :name => 'Dexter'
street.create[{:streetname => 'street1'},
{:streetname => 'street2'},
{:streetname => 'julianave'},
{:streetname => 'street3'}]
Question 1: Why is persons' data added differently than streets'? Is it just the tutorial that wants to show that there are many ways of adding data in the seeds.rb?
Question 2: The tutorial doesn't make the connections/joins in the seeds.rb. It does that in the rails console;
>>p1 = Person.find(1)
>>s1 = Street.find(1)
>>p1.streets << s1
Can't theese connections be made in the seeds.rb file?
Question 3: Would it be better to do this join with a "rich many_to_many-assocciation"?
Thanks for your time and patience with a beginner ;)
1) The first method is creating one object. The second method is creating multiple objects. However, for the second method you would need to do Street.create, not street.create.
2) Yes, you can do that in the seed file the same way.
3) The "Rich many-to-many" you're talking about is an association with a Join Model, I guess you're talking about. This is opposed to just a join table, which is what has_and_belongs_to_many does. To use a join model, you'll want to look up has_many :through. It's generally considered better to always use a proper join model, however I still use HABTM when I just need a quick, simple association. has_many :through allows for more options and more flexibility, but it is a little more complicated to setup (not that much, though). It's your decision.
One way that I like to create seed data for many-to-many associations is setting up one of the models, the adding a tap block that sets up the other models through the association.
Person.create!(:name => "Fubar").tap do |person|
3.times do |n|
person.streets.create!(:streetname => "street #{n}")
end
# OR
person.streets.create!([
{:streetname => "street 1"},
{:streetname => "street 2"},
... and so on
])
end
All tap is doing is executing the block with the object as it's only parameter. I find it convenient for seeds.
One other tip I would toss out there would be to have your model attribute names spaced on the words with underscores.
:street_name instead of :streetname
The difference is more profound when you start wanting to use some of the ActiveSupport helers that take model attributes and turn them into text strings for use in the UI.
e
:streetname.to_s.titleize # "Streetname"
:street_name.to_s.titleize # "Street Name"
And one last nitpick, you might want your join table to be addresses_people not addresses_persons since the rais inflector is going to pluralize person as people. The same would go for your controller on the Person model, PeopleController instead of PersonsController. Though maybe it will work with persons as well.
:person.to_s.pluralize # "people"
:people.to_s.singularize # "person"
:persons.to_s.singularize # "person"
I have a rails app (running on version 2.2.2) that has a model called Product. Product is in a has-and-belongs-to-many relationship with Feature. The problem is that I need have search functionality for the products. So I need to be able to search for products that have a similar name, and some other attributes. The tricky part is that the search must also return products that have the exact set of features indicated in the search form (this is represented by a bunch of checkboxes). The following code works, but it strikes me as rather inefficient:
#products = Product.find(:all, :conditions=>["home=? AND name LIKE ? AND made_by LIKE ? AND supplier LIKE ? AND ins LIKE ?",hme,'%'+opts[0]+'%','%'+opts[1]+'%','%'+opts[3]+'%','%'+opts[4]+'%'])
#see if any of these products have the correct features
if !params[:feature_ids].nil?
f = params[:feature_ids].collect{|i| i.to_i}
#products.delete_if {|x| x.feature_ids!=f}
end
I'm sorry that my grasp of rails/sql is so weak, but does anyone have any suggestions about how to improve the above code? Thanks so much!
First, i would recommend you to manually write a FeatureProduct model (and not use the default 'has_and_belongs_to_many')
EG
class FeatureProduct
belongs_to :feature
belongs_to :product
end
class Product
has_many :feature_products
has_many :features, :through => :feature_products
end
class Feature
has_many :feature_products
has_many :products, :through => :feature_products
end
For the search: You may find the gem SearchLogic to be exactly what you need. It has support for 'LIKE' conditions (it means that you can write in a more 'Rails way' your query). It also has support for performing a search with conditions on a related model (on your Feature model, to be more precise).
The solution would be something like:
search = Product.search
search.name_like = opt[0]
search.made_by_like = opt[1]
...
search.feature_products_id_equals = your_feature_ids
..
#product_list = search.all
There is also an excellent screencast explaining the use of this gem.
Good luck :)