I would like to use the group_by method, but instead of using a column in the database:
#posts.group_by(&:date)
I want to have something like:
#posts.group_by(&:author)
Where post belongs_to author.
I'm just not sure of the syntax?
You can write in your model:
def author_name
self.author.name
end
and then
#posts.group_by(&:author_name)
In this instance presumably you could actually just do
#posts.group_by(&:author_id)
More generally, you can pass a block to group_by, so if you need to group on some arbitrary logic:
#posts.group_by { |post|
... some code returning a value to group this post by ...
}
As a hopefully related aside, last time I looked, the implementation of group_by preserves the relative order of the data fed to it, which can be useful to remember as it allows you to sort the data in the 'flat' list of posts, and then when you group them the order is maintained within the groupings, rather than dealing with sorting withing the nested data output by group_by.
Related
I'm trying to add the ability to filter posts by keywords. I have it working if a post only includes one keyword, but it doesn't work when a post has multiple keywords. I think I understand why it doesn't work, but I can't figure out how to fix it.
This is the method in the controller:
KEYWORDS = ["authorlife", "lostlegacies", "ashiftinshadows", "snippets", "artwork", "random"]
def filter
filtered_posts = Post.all
KEYWORDS.each do |keyword|
if params[keyword] == true
filtered_posts = filtered_posts.where(keywords: keyword)
end
end
render json: filtered_posts
end
In the case where a post only has one keyword I think it works because my query is basically checking if the keywords for a post match the keyword in the loop. So if I only have keyword, let's say "keyword1" in the post and the keyword it's looking for is "keyword1" then it works. But if I have two keywords then it's trying to match "keyword1" but only finding posts that have "keyword1 keyword2" and because the second keyword is there it doesn't match. I'm not sure if I should be doing something with includes? or ILIKE. I've tried a couple different things and still can't figure this out.
The actual problem here is that you're most likely storing the keywords wrong. The only way using LIKE will actually fix the problem is if you're storing the keywords in a comma separated string or something simular. Storing multiple values in a column like that violates first normal form and you're really just introducing bugs in your application and hurting performance by using pattern matching. An example of these bugs is that LIKE %foo% will match foo_bar and foo_bar_baz, ooops.
A slightly better solution would be to use a native array or JSON/JSONB type column.
But the best solution would be to use a normalization table and join table to avoid duplicating the keywords.
First generate the model and join table
rails g model keyword text:string:unique
rails g migration create_posts_keywords_join_table posts keywords
Then setup the assocations:
class Keyword < ApplicationRecord
has_and_belongs_to_many :posts
end
class Post < ApplicationRecord
has_and_belongs_to_many :keywords
end
This will let you filter posts by keyword by doing a LEFT JOIN:
Post.joins(:keywords)
.where(keywords: { text: keywords })
I changed my query to this:
filtered_posts = filtered_posts.where("keywords like ?", "%#{keyword}%")
And it's working now. Not sure if there's a better way to do this.
If you want to get all the Posts that have a keyword in the KEYWORDS array, ActiveRecord can handle the array for you:
Post.where(keyword: KEYWORDS)
#this generates the sql query:
# select posts.* from posts where posts.keyword in ('authorlife','lostlegacies', ...etc)
I wondering if it is posible to use a model instance method as a where clause query. I mean. I have a model School with a method defined
class School < ActiveRecord::Base
def my_method
users.where(blablabla).present?
end
end
Is it posible to get something like:
School.all.where(my_method: true)
I know that I can do something like:
School.all.map{|x| x if x.my_method}
But this way has a huge penalization in performance compared to where query. Furthermore, the return of what I'm searching is an ActiveRecord Relation and map returns an array.
UPDATE:
Also there is another way to do it like:
School.all.joins(:users).where("users.attribute = something")
But this do not fit exactly what I want for several reasons.
Thanks in advance
I don't really know the relations between your models.
but where clause gets a hash of key - value.
So you can for example return the ID's of the users in a some kind of a hash and then use it.
def my_method
{user_id: users.where(blablabla).ids}
end
and use it:
School.all.where(my_method)
I have a Track table and a Section table. A track has many sections. Sections are connected to their respective task by the Section's :track_id, which corresponds to the Track's :id attribute.
<% #track = Track.find(params[:id]) %>
<% #sections = Section.find_by_track_id(#track.id) %>
In the code above I'm trying to find multiple sections that share the same :track_id attribute, but find_by_track_id() only returns the first. What's the best way to get all of them?
Thanks!
If your tracks and sections are related in this way, then the best way to relate them is by using the methods that come automatically from Rails' associations.
in this case, I expect in your model files, you have the following:
class Track < ActiveRecord::Base
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :track
end
Then you can get the sections for a track like this:
#track = Track.find(params[:id])
#sections = #track.sections
You're looking for where, which finds all records where a specific set of conditions are met.
#sections = Section.where(track_id: #track.id)
This is unrelated to your question, but you should set #sections and #track in your controller. As it seems like you're new to Rails, I'd highly recommend reading through the Rails Guides. They will help you immensely on your journey.
EDIT: I was solving for the general question of "Find multiple database objects by attribute in Rails?", which is how to find multiple database objects in the general case. #TarynEast's method is the way to go to find all of the sections for a track, or more generally, all of the objects that belong to the desired object. For the specific case you're asking for above, go with #TarynEast's solution.
Association
To extend Taryn East's answer, you need to look into ActiveRecord Associations.
In your model, if you have the following has_many relationship:
#app/models/track.rb
Class Track < ActiveRecord::Base
has_many :sections
end
#app/models/section.rb
Class Section < ActiveRecord::Base
belongs_to :track
end
This will set up a relational database association between your tracks and sections datatables.
--
Associative Data
The magic of Rails comes into play here
When you call the "parent" object, you'll be able to locate it using its primary key (typically the ID). The magic happens when Rails automatically uses this primary_key as a foreign_key of the child model - allowing you to call all its data as an append to the parent object:
#track = Track.find params[:id] #-> find single Track by primary key
#sections = #track.sections #-> automagically finds sections using the track primary key
This means if you call the following, it will work exactly how you want:
#sections.each do |section|
section.name
end
Where
Finally, if you wanted to look up more than one record at a time, you should identify which ActiveRecord method you should use:
find is to locate a single record by id
finy_by key: "value" is to locate a single record by your defined key/column
where is to return multiple items using your own conditions
So to answer your base line question, you'll want to use where:
#sections = Section.where track_id: params[:id]
This is not the right answer, but it should help you
<% #sections=#track.sections%>
Use find when you are looking for one specific element identified by it's id.
Model.find is using the primary key column. Therefore there is always exactly one or no result.
I've got one model with about 50 columns of measurement data, each with a different unit of measurement (ie. grams, ounces, etc.). What is a good way to associate units of measurement with columns in my database? The primary use for this is simply for display purposes. (Ruby on Rails)
EDIT: To clarify, my model is an object, and the attributes are different measurements of that object. So, an example would be if I had the model Car and the attribute columns :power, :torque, :weight, :wheelbase, etc. I would want car.power.unit to return hp and car.weight.unit to return lbs., etc. This way, I would be able to do something like this:
<%= car.power + car.power.unit %>
and it would return
400hp
Updated Answer
Since you're storing many columns of data, but each column is only one type, and your concern is strictly presentational, I would just use a decorator to accomplish what you need. See this railscast for an example of a great way to do this using Draper.
Basically, a decorator wraps your model with presentation specific methods, so instead of:
#CarsController.rb
def show
#car = Car.find(params[:id])
end
You would use
#CarsController.rb
def show
#car = CarDecorator.find(params[:id])
end
You would define a decorator like so:
class CarDecorator < ApplicationDecorator
decorates :car
def horsepower
model.power.to_s + "hp" #call to_s just in case
end
end
Then in your view any time you called #car.horsepower you would get 123hp instead of 123. In this way you can build a big long reusable list of presentation methods. You can share methods between objects using inheritance, and you can allow methods from the original model to be called as well. See the railscast and the docs etc. You can use Draper or you could roll your own presenter class if you don't want to use a library.
Previous Answer (Abridged):
I can see two nice, easy ways to do this:
1) Just add a text column for units to your data model. IE: to get "400hp" use [data.value,data.units].join
2) You could get a little richer association by having a Units model, perhaps with help from something like ActiveEnum.
You could add a unit model with a for attribute, where you save the attribute in the messurement, you want to apply the unit to. Example:
def Unit < ActiveRecord::Base
scope :for, lambda{|messurement| find_by_for( messurement.to_s ) }
end
This allows you stuff like:
<%= #car.torque + Unit.for(:torque).symbol %>
I do not know if this is of so much advantage, but its a way to solve your problem...
Hey,
Not a Rails noob but this has stumped me.
With has many through associations in Rails. When I mass assign wines to a winebar through a winelist association (or through) table with something like this.
class WineBarController
def update
#winebar = WineBar.find(params[:id])
#winebar.wines = Wine.find(params[:wine_bar][:wine_ids].split(",")) // Mass assign wines.
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
This will delete every winelist row associated with that winebar. Then it finds all of the wines in wine_ids, which we presume is a comma separated string of wine ids. Then it inserts back into the winelist a new association. This would be expensive, but fine if the destroyed association rows didn't have metadata such as the individual wine bar's price per glass and bottle.
Is there a way to have it not blow everything away, just do an enumerable comparison of the arrays and insert delete whatever changes. I feel like that's something rails does and I'm just missing something obvious.
Thanks.
Your problem looks like it's with your first statement in the update method - you're creating a new wine bar record, instead of loading an existing record and updating it. That's why when you examine the record, there's nothing showing of the relationship. Rails is smart enough not to drop/create every record on the list, so don't worry about that.
If you're using the standard rails setup for your forms:
<% form_for #wine_bar do |f| %>
Then you can call your update like this:
class WineBarController
def update
#winebar = WineBar.find(params[:id])
render (#winebar.update_attributes(params[:wine_bar]) ? :update_success : :update_failure)
end
end
You don't need to explicitly update your record with params[:wine_bar][:wine_ids], because when you updated it with params[:wine_bar], the wine_ids were included as part of that. I hope this helps!
UPDATE: You mentioned that this doesn't work because of how the forms are setup, but you can fix it easily. In your form, you'll want to rename the input field from wine_bar[wine_ids] to wine_bar[wine_ids_string]. Then you just need to create the accessors in your model, like so:
class WineBar < ActiveRecord::Base
def wine_ids_string
wines.map(&:id).join(',')
end
def wine_ids_string= id_string
self.wine_ids = id_string.split(/,/)
end
end
The first method above is the "getter" - it takes the list of associated wine ids and converts them to a string that the form can use. The next method is the "setter", and it accepts a comma-delimited string of ids, and breaks it up into the array that wine_ids= accepts.
You might also be interested in my article Dynamic Form Elements in Rails, which outlines how rails form inputs aren't limited to the attributes in the database record. Any pair of accessor methods can be used.