Right now I have this
def index
#trips = Trip.all
end
And I'm outputting data like so:
- #trips.order('created_at desc').first(4).each do |trip|
- trip.trip_images.first(1).each do |image|
= trip.title_name.titleize
However, I have a votable table (from acts_as_votable gem) associated to trips. I was wondering if I can only output trips where trips have a certain amount of votes?
I can get the votes like this:
- #trips.order('created_at desc').first(4).each do |trip|
= trip.get_likes.size #this is where I can get the likes
- trip.trip_images.first(1).each do |image|
= trip.title_name.titleize
EDIT
If I do this instead:
def index
#votes = ActsAsVotable::Vote.where(votable_type: 'Trip').group(:votable_id).count
#trips = Trip.where(#votes)
end
#votes gives me something like this:
{195=>1, 106=>1, 120=>1, 227=>1, 247=>1, 264=>1, 410=>1}
How do I get it where trip will only get the ids?
EDIT 2
I think I figured it out...
def index
#votes = ActsAsVotable::Vote.where(votable_type: 'Trip').group(:votable_id).count
#trips = Trip.where(id: #votes.keys)
end
I got some kind of output. Is there a better way?
Yesterday I answered similar question.
This is how you could get the id(s) of trip with certain amount of votes (you can use =, >, <= and so on):
trip_ids = ActsAsVotable::Vote
.where(votable_type: 'Trip')
.group(:votable_id)
.having('count(votable_id) > 1') #any number of votes
.pluck(:votable_id)
.uniq
Trip.where(id: trip_ids)
Have you considered making this a method in your Trip model?
Something like,
def popular_trip_images
Trip.select(:trip_images).where("likes > ?", 200)
end
Then use it something like,
...
trip.popular_trip_images.each do |image|
...
Edit:
However, I have a votable table (from acts_as_votable gem) associated to trips. I was wondering if I can only output trips where trips have a certain amount of votes?
Sorry, missed this part. The gem has a find_liked_items method but don't see offhand how to set something like liked > 400 etc.
I've been trying to work through the comments, but right now, I've gotten this code to work:
def index
#votes = ActsAsVotable::Vote.where(votable_type: 'Trip').group(:votable_id).count
#votes = #votes.select {|k,v| v > 1}
#trips = Trip.where(id: #votes.keys)
end
If someone else can come up with a better solution! I'll mark as correct.
Related
I have a resource :posts, which I show one at a time in show.html.erb
Suppose I have ten posts, each with an :id going from 1-10. If I delete post #2, then my posts will be 1,3,4,5,6,7,8,9,10. If I create ten posts and delete them all, then the next post :id would be [1,3..10,21] but I would only have 11 posts.
I want to show the post number that's in the application and put it in the view against a total number of posts. So if you were looking at post #3, it might have an :id of 3, but it is post #2 in the database.
Here's what I tried so far:
posts_controller.rb
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.all.count.to_i
#posts_array = Post.pluck(:id).to_a
...
end
views/posts/show.html.erb
<%= #post.id %> of <%= #total_posts %> /
models/post.rb
def next
Post.where("id > ?", id).order(id: :asc).limit(1).first
end
def prev
Post.where("id < ?", id).order(id: :desc).limit(1).first
end
However, showing the :id of a resource is a security issue so I don't know how to do it better.
How can I make it so the show.html.erb view only shows the current index order of the total amount of resources as compared to the post_id?
An efficient way to do this could be
# app/controllers/posts_controller.rb
def show
#post = Post.friendly.find(params[:id])
#total_posts = Post.count
#post_index = Post.where("id <= ?", #post.id).count
end
# app/views/posts/show.html.erb
. . .
<%= #post_index %> of <%= #total_posts %>
. . .
You should avoid loading all posts (or even their id) if you can. This will become more and more expensive as the number of posts grows and will eventually become a bad bottleneck for performance.
If you're trying to find the 'array index' of a record (so to speak) you can do this:
Agency.order(id: :asc).offset(params[:index]).limit(1)
You don't really want to do any other way because then it will load EVERY record into rails which will be very slow. It's better to ask the database for only a single record (which is what 'offset' does). Just replace params[:index] with whatever the name of the params is, whether its params[:id], etc.
I did just want to address one thing you said:
However, showing the :id of a resource is a security issue so I don't know how to do it better
That's not a security issue. The app should be designed in a way where the ID of a resource is not special or "secret." If you have an ID of a record, your controller should work such that it "authorizes" certain actions and won't let you do something you're not supposed to (like a user deleting a post).
If you REALLY need to do this, then just hide the ID and use a slug instead, like example.com/this-is-a-post-slug. This can be done quite easily
Edit To answer your specific question...
ids = Agency.order(id: :asc).pluck(:id)
#post_index = ids.find_index(#post.id)
#next_post = ids[#post_index + 1]
#prev_post = ids[#post_index - 1]
You can now use #post_index in your view.
Note: #prev_post and #next_post will be nil when the page doesn't exist (i.e. the "next post" when you're on the last page), so you will need to check that.
Just try it:
def show
...
#post = Post.friendly.find(params[:id])
#total_posts = Post.count # this will return integer type data
#posts_array = Post.pluck(:id) # you don't need to_a as .pluck returns array
...
For the next part you could write:
def next
self.class.where("id > ?", id).limit(1).first # this use of id is secured.
end
def prev
self.class.where("id < ?", id).order(id: :desc).limit(1).first
end
I created a simple web form where users can enter some search criteria to look for venues e.g. a price range. When a user clicks "find" I use active record to query the database. This all works very well if all fields are filled in. Problems occur when one or more fields are left open and therefore have a value of null.
How can I work around this in my controller? Should I first check whether a value is null and create a query based on that? I can imagine I end up with many different queries and a lot of code. There must be a quicker way to achieve this?
Controller:
def search
#venues = Venue.where("price >= ? AND price <= ? AND romance = ? AND firstdate = ?", params[:minPrice], params[:maxPrice], params[:romance], params[:firstdate])
end
You may want to filter out all of the blank parameters that were sent with the request.
Here is a quick and DRY solution for filtering out blank values, triggers only one query of the database, and builds the where clause with Rails' ActiveRecord ORM.
This approach safeguards against SQL-injection, as pointed out by #DanBrooking. Rails 4.0+ provides "strong parameters." You should use the feature.
class VenuesController < ActiveRecord::Base
def search
# Pass a hash to your query
#venues = Venue.where(search_params)
end
private
def search_params
params.
# Optionally, whitelist your search parameters with permit
permit(:min_price, :max_price, :romance, :first_date).
# Delete any passed params that are nil or empty string
delete_if {|key, value| value.blank? }
end
end
I would recommend to make method in Venue
def self.find_by_price(min_price, max_price)
if min_price && max_price
where("price between ? and ?", min_price, max_price)
else
all
end
end
def self.find_by_romance(romance)
if romance
where("romance = ?", romance)
else
all
end
end
def self.find_by_firstdate(firstdate)
if firstdate
where("firstdate = ?", firstdate)
else
all
end
end
And use it in your controller
Venue
.find_by_price(params[:minPrice], params[:maxPrice])
.find_by_romance(params[:romance])
.find_by_firstdate(params[:firstdate])
Another solution to this problem, and I think a more elegant one, is using scopes with conditions.
You could do something like
class Venue < ActiveRecord::Base
scope :romance, ->(genre) { where("romance = ?", genre) if genre.present? }
end
You can then chain those, which would work as an AND if there is no argument present, then it is not part of the chain.
http://guides.rubyonrails.org/active_record_querying.html#scopes
Try below code, it will ignore parameters those are not present
conditions = []
conditions << "price >= '#{params[:minPrice]}'" if params[:minPrice].present?
conditions << "price <= '#{params[:maxPrice]}'" if params[:maxPrice].present?
conditions << "romance = '#{params[:romance]}'" if params[:romance].present?
conditions << "firstdate = '#{params[:firstdate]}'" if params[:firstdate].present?
#venues = Venue.where(conditions.join(" AND "))
I'm getting a weird result when doing a search I've implimented from Railscast #111 (Advanced Search).
The search results are coming through OK from the entries that I have, but when the result is more than 1 (Iv'e only tested for two results) - it doubles the results. So when I expect two results, I get 4 (rendering out in a table).
The weird part is that when I expect 1 result, it only renders the 1 result.
I have a feeling it has something to do with my 'Search.rb' file which gives the search parameters. Can someone else shed some light on why this would be giving duplicate results?
class Search < ActiveRecord::Base
def entries
#entries ||= find_entries
end
private
def find_entries
entries = Entry.order(:firstname)
entries = entries.where("firstname like ?", "%#{firstname}%") if firstname.present?
entries = entries.where("lastname like ?", "%#{lastname}%") if lastname.present?
entries
end
end
I am searching by either firstname or lastname, but the entries have more fields in them.
Try
def find_entries
entries = Entry.order(:firstname)
if !firstname.blank?
entries = entries.where("firstname like ?", "%#{firstname}%")
if !lastname.blank?
entries = entries.where("lastname like ?", "%#{lastname}%")
end
else
if !lastname.blank?
entries = entries.where("lastname like ?", "%#{lastname}%")
end
end
entries
end
I'm hacking away at a rails project and I wanted to modify the number of items that end up on a particular page. The page gets populated via an array of items.
For the life of me I can't figure out how to make it show only 2 instead of 4 items.
In the haml file there is this section:
%ul.story-list
- #stories.each do |story|
%li
- unless story.image.blank?
.img-container{ class: ((story.video.blank?)? "": "video-container") }
= image_tag(story.image_url, alt: story.name, class: ((story.video.blank?)? "": "js-has-video"), :video => story.video)
.story-data
%h4= story.name
%h5.location= story.location
%p.quote= story.story
- if story.get_connected?
= link_to 'Get Connected', connect_path
- elsif story.gather_supplies?
= link_to 'Gather Supplies', supplies_path
- elsif story.make_a_plan?
= link_to 'Make a plan', plan_path
The page shows up (on the server) with four story items, I want it to only show two. I was expecting to open the haml file and just delete some lines (or comment them out). I'm so confused.
So, I suspect the number of stories comes from a controller or something like that. ..but maybe it is coming from the placeholder data on the server?
In case you are inspired to help me, all the code is here
https://github.com/city72/city-72
The exact page I'm trying to modify is this one, I want it to only have two stories:
http://collier72.herokuapp.com/stories
Weirdly, in my local environment I can't edit the stories at all. That's what makes me thing the number of items comes from the data.
The stories controller is this tiny little file that doesn't specify the number of stories:
class StoriesController < ApplicationController
after_filter :static_content
def index
all_stories = EmergencyStory.order("index,id ASC").all
#selected_story = all_stories.select {|s| s.selected}.first
#stories = all_stories.collect.select {|s| !s.selected}
end
end
Open up this file:
https://github.com/city72/city-72/blob/master/app/controllers/stories_controller.rb#L8
Change that line from this:
#stories = all_stories.collect.select {|s| !s.selected}
to this:
#stories = all_stories.collect.select{|s| !s.selected}.slice(0,2)
From what I can tell, the fact it is returning 4 isn't intentional, it's just what is in the database. The slice(0,2) will return the first two items.
First, you have 3 stories that you are looking for, not 2. You have your #selected_story and then the remaining #stories. Second, you are retrieving ALL of the stories which will not scale when you get many stories in the database, so rendering this page will slow down over time. So you need to limit the number of records being returned by the database.
Get the selected story.
Then get the two next stories.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.where(selected: true).first
#stories = EmergencyStory.where(selected: false) # don't get selected
.limit(2) # limit records returned
.order("index,id ASC")
.all
end
end
If you were to further refine this you should put those two queries into methods into EmergencyStory.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.selected_story
#stories = EmergencyStory.recent_stories
end
end
class EmergencyStory < ActiveRecord::Base
def self.selected_story
where(selected: true).first
end
def self.recent_stories
where(selected: false).limit(2).order('index,id ASC').all
end
end
This my code:
#statuses = []
current_user.friends.each do |f|
#statuses = #statuses + f.statuses
end
#sorted_statuses = #statuses.sort_by { |obj| obj.created_at }
I'm taking all my friends statuses from the database and puting them on the wall.
I'm trying to show them from the new (at the top) to the old (at the bottom), buy right now its vice versa. Please let me know how to change it.
No need to pre-declare the array, or post process on another line. the ordering can be done via database. The ruby way is:
#statuses = current_user.friends.order('created_at DESC').collect {|f| f.statuses}
You can even define the default order in your model with default_scope
class Person < ActiveRecord::Base
default_scope order('last_name, first_name')
end