This method is in tutor_session model
def self.tutor_minutes_ranking(users, start_date, end_date)
joins(:session_users)
.select('session_users.user_id, SUM(session_length)')
.where("session_users.user_id IN (?) and tutor_sessions.created_at > ? and tutor_sessions.created_at < ?", users, start_date.to_datetime, end_date.to_datetime)
.group('session_users.user_id')
.sum(:session_length, :order => 'sum_session_length DESC')
end
Ok, trying to get the total amount of minutes tutored in a month
This method is not written by me.
But I am trying to use it so I can display the total.
This is what I have in a different controller (ie, not tutor_session controller):
#total_minutes_month = TutorSession.total_minutes_for_user(current_user)
This is what I have in the view:
<% #total_minutes_month.each do |i| %>
<%= i.first %>
<% end %>
This is the error that comes up:
wrong number of arguments (1 for 3)
Obviously I didn't fill out the parameters for the second or third argument as displayed in the method.
The thing is, I'm not sure how to fill out the start_date, and end_date.
Advice?
Try this for the total time in this month:
TutorSession.total_minutes_for_user(current_user,
Time.now.beginning_of_month,
Time.now)
and something like this for the last month:
TutorSession.total_minutes_for_user(current_user,
1.month.ago.beginning_of_month,
1.month.ago.end_of_month)
#spickermann's answer is correct to solve your arguments error
However, when you mention you're then receiving an undefined_method error for the .each statement - the problem will be caused by #total_minutes_month having only the value of 0 - meaning there's no array/hash to cycle through
If you want to display the data you have from your method as it is now, you'll be best doing something like:
#app/views/controller/view.html.erb
<%= #total_minutes_month #-> should display total minutes for user %>
I'd personally attach the method to the user model's association, so you can call current_user.tutor_sessions.first.total_minutes_month:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :tutor_sessions, through :session_users do
def self.tutor_minutes_month(start, end)
joins(:session_users)
.select('session_users.user_id, SUM(session_length)')
.where("session_users.user_id IN (?) and tutor_sessions.created_at > ? and tutor_sessions.created_at < ?", id, start.to_datetime, end.to_datetime)
.group('session_users.user_id')
.sum(:session_length, :order => 'sum_session_length DESC')
end
end
end
Something like this will allow you to call current_user.tutor_sessions.first.total_minutes_month to give you the minutes per tutor session
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
In my Rails 4 app, I have a Post and a Calendar model: a calendar has_many posts and a post belong_to a calendar.
In the post show.html.erb view, located at /posts/:id, I want to allow users to navigate back and forth between the posts of the calendar to which the current post belongs to, with a "previous post" button and a "next post" button.
Here are my routes:
resources :calendars do
resources :posts, shallow: true
end
end
I know I will have something like that in my post show.html.erb view:
<% if #calendar.posts.count > 1 %>
<%= link_to "< Previous", #previous_post %> | Post Preview | <%= link_to "Next >", #next_post %>
<% else %>
Post Preview
<% end %>
So far, in my PostsController, I came up with:
def show
#calendar = Calendar.find_by_id(#post.calendar_id)
#posts = #calendar.posts
#previous_post = #post.previous
#next_post = #post.next
end
However, I am struggling to come up with the right definition of the previous and next methods (that you can see in the PostsController code above).
These methods must allow me to find — respectively — the post that is right before or right after the current post in #calendar.posts
How can I achieve that?
Your solution with next and previous commands relative to date is good, but doesn't work completely because you need to order the results chronologically as well. The where will filter the ones you don't want, but you need to make sure that the rest are in the order you desire.
So you'd have something like:
def next
calendar.posts.where("time > ?", time).order(:time).first
end
def previous
calendar.posts.where("time < ?", time).order(time: :desc).first
end
Edit:
I'm assuming that time is a DateTime. If it is a Time ONLY without date information, you'll urgently want to change that to a DateTime field.
Not a very ideal kind of solution, but in your case, should work and give you the previous and next posts of a #post from a #posts array.
Add get_next_previous_posts helper method and use it in your controller's show method:
def show
#calendar = Calendar.find_by_id(#post.calendar_id)
#posts = #calendar.posts
#previous_post, #next_post = get_next_and_previous_posts(#posts, #post)
end
private
def get_next_and_previous_posts(posts, current_post)
next_post = posts.detect { |p| p.id > current_post.id }
prev_post = posts.reverse.detect { |p| p.id > current_post.id }
[prev_post, next_post]
end
It sounds like you need some pagination. will_paginate or kaminari gems should do the trick (I prefer kaminari).
This is what I ended up doing:
post.rb
def next
calendar.posts.where("id > ?", id).first
end
def previous
calendar.posts.where("id < ?", id).last
end
posts_controller.rb
def show
#calendar = Calendar.find_by_id(#post.calendar_id)
#previous_post = #post.previous
#next_post = #post.next
end
This is not an ideal solution, but it is working.
—————
UPDATE:
Because posts must be displayed in chronological order, I had to change the above code to:
#post.rb
def next
calendar.posts.where("time > ?", time).first
end
def previous
calendar.posts.where("time < ?", time).last
end
However, this is not working perfectly, as you can see on this gif:
It is almost as if the previous button still works based on post id and not time.
I did restart my server in case that was the problem, but it did not fix it.
Any idea how I can improve on this code?
—————
UPDATE 2: based on Richard Seviora's answer, I also tried this:
#post.rb
def next
calendar.posts.where("date > ? AND time != ?", date, time).order(:time).first
end
def previous
calendar.posts.where("date < ? AND time != ?", date, time).order(time: :desc).first
end
Still not working as expected.
—————
Rails 3.2 Twitter App.
SOLUTION: Props to correct answer. I overlook the simple stuff sometimes. Ended up putting a line into my huge view. It was easier to me. Looks like this.
<% #ribbits.each do |ribbit| %>
<% if ribbit.user != current_user%>
<% if ribbit.user.following? current_user %>
<% if ribbit.created_at > (Time.now - 1.day) %>
etc.
I've got one last feature I'm trying to figure out. If a user's most recent status was created more than 24 hours ago, I'd like to create a new status of "yadayadayada" for them.
Here's how the most recent status is displayed.
users_controller.rb
def buddies
#ribbits = Ribbit.where("(ribbits.user_id, ribbits.created_at) IN (SELECT user_id, MAX(created_at) FROM ribbits WHERE user_id IN (?) GROUP BY user_id ORDER BY MAX(created_at) DESC LIMIT 10)", buddies_ids).order("created_at DESC")
/users/buddies.html.erb
<% #ribbits.each do |ribbit| %> ... etc
Something like this is either going in my users_controller.rb or ribbits_controller.rb
if #ribbits.user.ribbit created_at > (Time.now - 1.day)
Ribbit.new(:status => "sdfaklsdf")
end
Ribbits are tweets.
If you don't need this "default" status in the database, then you can just push the logic into the User model.
Something like:
class User < ActiveRecord::Base
def current_ribbit
if ribbit created_at > (Time.now - 1.day)
default_ribbit
else
ribbit
end
end
def self.default_ribbit
Ribbit.new(:status => "sdfaklsdf")
end
end
I think what you are looking of is a scheduler. You can use Delayed Job or Sidekiq for this. Sidekiq let's you create jobs at a specific time.
My Tasks belongs to different models but are always assigned to a company and/or a user. I am trying to narrow what gets displayed by grouping them by there due_at date without doing to many queries.
Have a application helper
def current_tasks
if user_signed_in? && !current_company.blank?
#tasks = Task.where("assigned_company = ? OR assigned_to = ?", current_company, current_user)
#current_tasks = #tasks
else
#current_tasks = nil
end
end
Then in my Main view I have
<%= render :partial => "common/tasks_show", :locals => { :tasks => current_tasks }%>
My problem is that in my task class I have what you see below. I have the same as a scope just named due_today. when I try current_tasks.due_today it works if I try current_tasks.select_due_today I get a undefined method "select_due_tomorrow" for #<ActiveRecord::Relation:0x66a7ee8>
def select_due_today
self.to_a.select{|task|task.due_at < Time.now.midnight || !task.due_at.blank?}
end
If you want to call current_tasks.select_due_today then it'll have to be a class method, something like this (translating your Ruby into SQL):
def self.select_due_today
select( 'due_at < ? OR due_at IS NOT NULL', Time.now.midnight )
end
Or, you could have pretty much the same thing as a scope - but put it in a lambda so that Time.now.midnight is called when you call the scope, not when you define it.
[edited to switch IS NULL to IS NOT NULL - this mirrors the Ruby in the question, but makes no sense because it will negate the left of the ORs meaning]
So I have some posts, and would like to show n..m most recent entries in the sidebar (these numbers being set in a config)
I can get the latest n records easily enough
class Post < ActiveRecord::Base
default_scope :order => "created_at DESC"
scope :published, lambda { where("blog_entries.created_at <= ?", Time.zone.now) }
scope :latest, lambda { |n| published.limit(n) }
end
#posts = Post.latest(6)
But what I'd like is
#posts = Post.published.limit(6, 12)
but this gives wrong number of arguments, so is there any way in AR? Right now I'm playing with will_paginate, but it seems hacky to use it for this.
Ok, so the answer is, I think:
#posts = Post.published.limit(6).offset(5)
It will retrieve 6 posts, starting from the sixth.
edit2: About the limit([6, 12]), I find that strange:
attr_accessor :limit_value
def limit(value)
relation = clone
relation.limit_value = value
relation
end
def build_arel
...
arel.take(connection.sanitize_limit(#limit_value)) if #limit_value
...
end
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
elsif limit.to_s =~ /,/
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
else
Integer(limit)
end
end
So I don't really see how it works with an array. But I obviously missed something. Do you see what?
For rails 5 (not sure for rails 4). offset(x).limit(y) works correctly. limit(y).offset(x) still behaves as described in other answers.