I've got a list of all my articles, ordered by client name.
articles_controller.rb - index
#articles = Article.order('client')
In my show page, how do I get the next and previous article in that order?
If you already have the article for the show action, you could use 'index' to get the position of this article in the Array returned by 'Article.order('client').all'
Somehow like this:
#article = Article.find(params[:id])
#article_list = Article.order(:client).all
index = #article_list.index(#article)
Based on this you can calculate the previous and next article simple enough. Not sure if this is the smartest solution, but it should work.
have two scopes in your model
class Article < ActiveRecord::Base
scope :order_by_id, lambda {
{:order => "id desc"}
}
scope :order_by_client, lambda {|client_id|
{:order => "client_id desc"}
}
end
and
#articles = Article.order_by_client(1).order_by_id
with validations you could do something like
next article (say the current article index is 1)
#articles(current_index + 1) unless #articles(current_index + 1).nil?
previous article (say the current article index is 2)
#articles(current_index - 1) unless #articles(current_index - 1).nil?
** havent tried the code but should work
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
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.
Say, I have a method called posted_listings, which is supposed to run an ActiveRecord query and return a collection of User.listings where posted: true, with posted? being a Listing class method. So far I have been doing:
class Bar < ActiveRecord::Base
def posted_listings
posted_listings = []
listings.each { |listing| posted_listings << listing if listing.posted? }
posted_listing
end
end
but each time this query runs I start feeling really bad about my skills (or lack of thereof). What is the most efficient way to return a collection of posted listings?
Edit:
posted? is not an attribute, its a class method:
class Listing < ActiveRecord::Base
def posted?
true if quantity >= 1 && has_sellers?
end
end
def has_sellers?
sellers.count >=1 #association to Seller
end
I would recommend adding a scope to your Listing model like this:
scope :posted, -> { where(posted: true) }
Then you can get all posted listings like this:
#user.listings.posted
You can learn more about scopes here if you are interested.
UPDATE
Try this scope instead:
def self.posted
joins(:sellers)
.where('posted = ? AND quantity > ?', true, 0)
.group('listings.id')
.having('COUNT(sellers.id) > ?', 0)
end
Your question is not so clear for me.
You may try:
User.listings.where(posted: true)
to get all users' posted Listings.
Or, saying #useris an User instance:
#user.listings.where(posted: true)
to get posted Listings from an specific user.
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
I'm a novice in ruby-on-rails.
I have an applications counting distance between metro station and a ATM.
There's two models with many-to-many relation: Station, Cashpoint. And there's a controller SHOW, that should get the station and show ATMs in order there's proximity to the station.
class StationsController < ApplicationController
def show
#station = Station.find(params[:id])
#cashpoints = #station.cashpoints.find(:all)
respond_to do |format|
format.html
end
end
end
Also there's a helper that counts distance using Google Directions API.
module StationsHelper
def count_distance(origin,destination)
...
return {:text => ... # 1 min
, :value => ... # 60 (seconds)
}
end
end
All this works properly.
But I'm wondering how to order ATMs by :value returned by StationsHelper?
I tried to write something in controller similar to:
#cashpoints = #station.cashpoints.find(:all,
:order => count_distance(#station.address, cashpoint.address)[:value])
But it's evidently doesn't work 'cause I have know idea how to link single cashpoint object
to count_distance method parameter.
May be you can help me, it appears that my project structure is wrong to do this.
Try this:
#cashpoints = #station.cashpoints.find(:all).sort_by { |c| count_distance(c.address, #station.address) }