:order by has_many association latest entry? how? - ruby-on-rails

rails is so much fun until you need to find a solution and it takes three days with no luck! ;-(
How do I order by the latest entry in has_many association for each entry?
Explanation:
I do a ranking of a Model Video by an associated Model Score (with a field ranking_score). The database is a postgreSQL.
Because I will frequently add a score for a video I need to order the videos by the latest score entry for each video.
Video.all(:include => :scores, :order => "scores.ranking_score DESC")
...works as expected until I add a new score entry for a video (I don't update, because I will have statistics on the ranking).
i also tried with all varieties (DESC & ASC) and I reordered the priorities but without luck:
Video.all(:include => :scores, :order => "scores.created_at DESC, scores.ranking_score")
and
Video.all(:include => :scores, :order => "scores.ranking_score, scores.created_at")
and so on.
I searched the web, tried to figure out postgreSQL commands and so on. But beeing a total noob (on rails for 6 weeks now) I need help, please.
Here is my code:
Video Model:
class Video < ActiveRecord::Base
.
has_many :scores
.
end
Score Model:
class Score < ActiveRecord::Base
attr_accessible :ranking_score, :video_id
belongs_to :video, :foreign_key => :video_id
def position
self.class.count( :conditions => ['ranking_score >= ?', self.ranking_score])
end
end
Video Controller:
class VideosController < ApplicationController
def index
setup_videos
#video ||= Video.new
respond_to do |format|
format.html # show.html.erb
format.json { render json: #video }
end
end
.
private
def setup_videos
#videos = Video.all(:include => :scores, :order => "scores.ranking_score DESC")
#user = current_user
end
end
As you can see to verify the ranking I included a method position, because I need a user specific view where only user videos get listed, but with the overall ranking shown (different problem, because like this it does not work with multiple entries for each video)
_ranking.html.erb (rendered whithin "index") --- without HTML Tags:
<% #videos.each.with_index do |video, index| %>
<%= index.+1 %>
ranking <%= video.scores.last.ranking_score %>
postition <%= video.scores.last.position %>
<% end %>
<% #user.videos.count %>
Thank you for your time!
joh

I'm having troubles understanding your requirements. However - have you thought of caching the highest rank with each of the video records?
You could add another column in video called highest_ranking that gets updated in an Score after_save operation or similar (depending on the score update frequency).
This will save you the trouble of joining to scores to order the video.

Related

ROR: Search by Username and display their posts

I am learning Ruby on Rails and have a search form set up and its working. On the pins index view I can search for pins(posts) by their title. However if I wanted to search by Username which is not in the pins table and display the results on the Pins index page how would I do this? How do I access an attribute from a different table? (Sorry for the newbie attempt at explaining my issue)
Pins controller
def index
#pins = Pin.search(params[:term])
end
Pin Model
def self.search(term)
if term
where('description LIKE ?', "%#{term}%")
else
order('id DESC')
end
end
_search.html.erb
<%= form_tag(pins_path, method: :get) do %>
<%= text_field_tag :term, params[:term] %>
<%= submit_tag 'Search', description: nil %>
<% end %>
Assuming you have set up something like
class User < ApplicationRecord
has_many :pins
# the username is stored in the attribute 'username'
end
class Pin < ApplicationRecord
belongs_to :user
end
you may do the following
# PinsController
def index
terms = params[:term]
username = params[:username]
#pins = Pin
#pins = #pins.where("description LIKE '%?%'", term) if term
#pins = #pins.includes(:user).where("users.username LIKE '%?%'", username) if username
# you may want to sort by id anyway
#pins = #pins.order('id DESC')
end
Note that I put the code straight to the controller for brevity. You may refactor this to use your search method in pin model.
# _search.html.erb
<%= form_tag(pins_path, method: :get) do %>
<%= text_field_tag :term, params[:term] %>
<%= text_field_tag :username, params[:username] %>
<%= submit_tag 'Search', description: nil %>
<% end %>
In case you want to do some more searching and filtering you may have a look at the ransack gem although I think you're going the right path in trying to figure this out yourself.
Although those railscasts episodes are from the past I think they are applicable to the current rails versions. Anyway one can get the point from them
http://railscasts.com/episodes/37-simple-search-form
http://railscasts.com/episodes/111-advanced-search-form
http://railscasts.com/episodes/240-search-sort-paginate-with-ajax
Another good resource is gorails.com (not affiliate in any way!!). I can highly recommend them as a resource for learning
Provided you have an association between User and Pin
class User
has_many :pins
end
class Pin
belongs_to :user
end
You can join :user from Pin and set conditions on the association:
Pin.joins(:user).where('users.username ? AND awesome = ?', 'Max', true)
# or the preferred method
Pin.joins(:user).where(user: { username: 'Max', awesome: true })
Note that we use users.username and not user.username when writing a SQL string you're specifying the table name - not the association.
To search for pins based on the username you could do:
def self.by_username(term)
joins(:user).where('users.username LIKE ?', "%#{term}%")
end

Splitting and array using formtastic. Error: undefined method `map'

I'm trying to turn my choices column from my Questionnaire table into separate strings as multiple choice.
Questionnaire Controller:
class QuestionnairesController < ApplicationController
def index
#questions = Questionnaire.find(params[:category_id])
#params[:category_id]= <%=category.id%>
#category = Category.find(params[:category_id])
#videos = VideoClue.find(params[:category_id])
###This finds all the questions from the question table by their category_id. Whenever I select a category, it matches the question related to the category
render :show
###render :show Renders Html page
end
def choose_answer
# binding.pry
#questions = Questionnaire.find(params[:id])
#params[:id] = /:id = /1
render :choose_answer
end
end
Questionnaire Model:
class Questionnaire < ActiveRecord::Base
belongs_to :categories
end
Chose_answer.html.erb
<h1>Congrats You Hit The Choices Page!</h1>
<%= semantic_form_for #questions.choices do |c| %>
<%= c.inputs do %>
<%= c.input :choices, :as => :check_boxes , :collection =>
[#questions.choices].map(&:inspect).join(', ') %>
<%end%>
<%end%>
Questionnaire table seed:
Questionnaire.create({question: "In that year did MTV (Music Television)
premiere and what was the first music video the channel aired?", choices:
["1982 Michael Jackson 'Bille Jean'", "1984 Madonna 'Like a virgn'", "1981
The Buggles 'Video Killed The Radio Star'"], correct_answer:"1981 The
Buggles 'Video Killed The Radio Star' ", category_id:1})
#question.choices returns
["1982 Michael Jackson 'Bille Jean'", "1984
Madonna 'Like a virgn'", "1981 The Buggles 'Video Killed The Radio Star'"],
How do I separate the strings from the choices column array and use formtastic to turn the strings into a mutiple choice format?
<%= c.input :choices, :as => :check_boxes , :collection => #questions.choices %> should do what you want here, no need to modify it if it's already an array IIRC, unless I'm misunderstanding the situation.

Rails 4 Has One Update Pb

I have a bit of a problem with a "has one" association on my app.
What I want to achieve is to be able to attach an optional quote to the topic. The quote can only be used once (in other words, if it's used for topic 1, it can't be used for any other topics).
I have a Topic model and a Quote model.
Topic has one quote.
Quote belongs to topic.
I also want to be able to attach a quote to other models (ex. Profile Model).
I'm really confused on what to do on my "edit topic" view as well as in the controller. I thought it would work like a "one to many" association, which I had no problem configuring. Somehow the "has one" is more complicated (for me!)
What I'd like is to have in the "edit topic" view a radio list of the available quotes which I can freely update. (Same for the "new topic" view).
My current controller:
def edit
#topic = Topic.find(params[:id])
#quote = #topic.quote
#packages = #topic.packages
#books = #topic.books
#tasklists = #topic.tasklists
#links = #topic.links
#terms = #topic.terms
end
def update
#topic = Topic.find(params[:id])
if #topic.update_attributes(topic_params)
flash[:success] = t('helpers.success-update', model: "topic")
redirect_to backend_topics_url
else
render partial: 'edit'
end
end
def topic_params
params.require(:topic).permit(:topic_id, :theme_id, :cover, :topic_status, :topic_access, :slug, *Topic.globalize_attribute_names, :quote_attributes => [:id, :topic_id], :package_ids => [], :book_ids => [], :link_ids => [], :tasklist_ids => [], :term_ids => [])
end
My current Topic model:
has_one :quote
accepts_nested_attributes_for :quote
My current Quote model:
belongs_to :topic
And my "Edit Topic" view:
<h4>Quote</h4>
<% if #quote %>
<h5>Current quote</h5>
<%= #quote.quote %> <%= link_to('[change]', '#') %>
<% end %>
<%= f.input :quote, :collection => Quote.all, :label_method => :quote, :label_value => :id, :checked => #quote.id, as: :radio_buttons %>
I'm sure there is something obvious that I'm missing but I can't figure out what.
Any ideas?
Thanks!
- Vincent
First off, if you want to have quote belong to multiple models, you will need a polymorphic association. Otherwise, you would need to add multiple foreign ids to quote like this: topic_id, profile_id etc and that will get messy fast. You can view a screencast on polymorphism here: http://railscasts.com/episodes/154-polymorphic-association-revised
has_one and belongs_to is basically the exact same as has_many and belongs_to except you are only dealing with 1 record instead of a collection of records.
For your current setup - in your edit action you need to fetch all the quotes that are not associated to any Topics. You can do that like this:
#available_quotes = Quote.where(topic_id: nil)
and then:
<%= f.input :quote, :collection => #available_quotes, :label_method => :quote, :label_value => :id, :checked => #quote.id, as: :radio_buttons %>
instead of
Quote.all in your form which is returning all quotes.
If you move to a polymorphic model, watch the screencast, and you would replace "commentable_id" in the screencast with something like "quotable_id" and then in your edit action to find the unassigned quotes you would do this:
#quotes = Quote.where(quotable_id: nil)

Rails: How to only allow User to apply to job only once?

I am creating a job board, and I don't want to allow the users the option to apply for the same job twice. How can I limit this?
app/views/jobs/job.html.erb
<% if applied_to_this_job? %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
app/helpers/jobs_helper.rb
def applied_to_this_job?
JobApplication.exists? user_id: current_user.id
end
Obviously this doesn't work because it checks if this user has applied to any job. How Can I check to see if the current user has applied to the job being viewed.
Also, how can I limit this at the controller level so that the user can't go to job_application/new and get to the form.
You would use a before_filter in the controller action.
class JobsController < ActionController::Base
before_filter :has_applied?, only: [new, create]
....
private
def has_applied?
if JobApplication.where(user_id: :current_user.id, job_id: params[:job_id]).any?
redirect_to :index, alert: "You have already applied"
end
end
end
This would allow the user to visit /jobs/new and post the application to /jobs/create unless they have applied. If they have applied, they will be redirected to the index in the sample code.
Also as another answer has noted, it would be wise to pass in the job id as well. Updated sample code above to reflect.
You need to check and see if the JobApplication object is for this #job try:
JobApplication.where( user_id: current_user.id, job_id: #job.id ).exists?
Although what you've accepted will work, I think it's somewhat of a surface-level fix.
You'll be much better using validators to determine if the user can actually create another job application. This will protect against any problems with the business logic in your "front-end" views
Here's how I'd handle it:
--
Uniq
#app/models/user.rb
class User < ActiveRecord::Base
has_one :job_application
end
#app/models/job_application.rb
class JobApplication < ActiveRecord::Base
belongs_to :user
validates :user_id, uniquness: true
end
You may also wish to give your database a uniq index for your user_id column:
> $ rails g migration AddUniqueIndex
#config/db/add_unique_index.rb
class AddUniqueIndex < ActiveRecord::Migration
def change
add_index :job_applications, [:job_id, :user_id], unique: true
end
end
This will give you a highly efficient DB-level uniqueness index - meaning that if you try and add any more applications than is permitted, it will either fail silently, or come back with an error.
Controller
The structure of the controller would allow you to be less stringent about the accessibility of the job_application functionality:
#app/views/jobs/job.html.erb
<% if current_user.has_applied?(params[:job_id]) %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
#app/models/user.rb
class User < ActiveRecord::Base
has_many :job_applications
def has_applied?(job_id)
job_applications.find job_id
end
end

Voting update on Ruby on Rails

Right now, I'm in the middle of building a social media app on Ruby on Rails, i have implemented a 5 point voting system. Where you can vote the news posted on the site from 1-5, what I'd like to know is, What is the best approach at handling the updates on the voting system.
In example. If a user already voted in an article I'd like to bring back score he gave in the article and soft-lock the voting (since i only allow 1 vote per user and i allow to change your vote at any time), but if he hasn't I'll bring up the article with the the voting on 0.
I know a way to accomplish this, i could do it in the view, and check if the current user has already voted on this article i would send them to the EDIT view otherwise to the SHOW view. (I think)
Anyways, what would be the "correct" approach to do this?
EDIT: I forgot to say that the voting combo box it's a partial that I'm rendering. Am i suppose to just update the partial somehow?
EDIT2:
class Article < ActiveRecord::Base
has_many :votes
belongs_to :user
named_scope :voted_by, lambda {|user| {:joins => :votes, :conditions => ["votes.user_id = ?", user]} }
end
class User < ActiveRecord::Base
has_many :articles
has_many :votes, :dependent => :destroy
def can_vote_on?(article)
Article.voted_by(current_user).include?(article) #Article.voted_by(#user).include?(article)
end
end
Create a method in the User model that responds true if the user can vote on an article:
class User < ActiveRecord::Base
...
def can_vote_on?(article)
articles_voted_on.include?(article) # left as an exercise for the reader...
end
end
In the view, render a form if the user can edit, otherwise render a normal view:
<% if #user.can_vote_on?(#article) %>
<%= render :partial => "vote_form" %>
<% else %>
<%= render :partial => "vote_display" %>
<% end %>
Or you could handle the whole thing in the controller, and render separate templates for the form version and the normal version. The best approach depends on the specifics of your situation.
EDIT2
As you discovered, current_user doesn't work in the model. This makes sense, because the can be called from migrations, libraries, etc., where there is no concept of a session.
There's no need to access the current user anyway, since your instance method is (by definition) being called on an instance. Just refer to self in the model, and call the method from the view on current_user, which is an instance of User:
(in the model)
def can_vote_on?(article)
Article.voted_by(self).include?(article)
end
(in the view)
<% if current_user.can_vote_on?(#article) %>
Or you could substitute #user for current_user if the controller assigns it.
One last thing, I think your named scope should use user.id, like so:
named_scope :voted_by, lambda {|user| {:joins => :votes, :conditions => ["votes.user_id = ?", user.id]} }

Resources