Rendering most recent articles rails 4 - ruby-on-rails

Its currently listing my oldest articles at the top and I want to do the opposite. I think I need to order it as created_at somewhere but I have yet to make it work. I know this is easy but I'm still a newbie. Thanks
Currently I have
<div class="bit-75">
<h2 id="title"><%= link_to article.title, article_path(article) %></h2>
<br>
<ul id="article-links">
<div id="article-image"><%= image_tag article.image_url %></div>
<br>
<li id="article-text"><%= article.text %></li>
<br>
<%= article.created_at %>
<br>
<% if admin_signed_in? %>
<li><%= link_to 'Edit', edit_article_path(article) %></li>
<li><%= link_to 'Destroy', article_path(article),
method: :delete, data: { confirm: 'Are you sure?'} %></li>
<li><%= link_to 'New article', new_article_path %></li>
<% else %>
<li><%= link_to 'Make a Comment', article_path(article) %></li>
</ul>
<% end %>
article.rb
class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
mount_uploader :image, ImageUploader
end
articles controller
def new
#article = Article.new
end
def index
#article = Article.all
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
def edit
#article = Article.find(params[:id])
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def show
#article = Article.find(params[:id])
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end

In your Article model, article.rb, you can set a default_scope like this:
default_scope -> { order('created_at DESC') }
However, this method will sort the articles like this on all pages. If you only want to sort them like this on one action, say your def index, then something like this might work better.
#articles = Article.order('created_at DESC')
Like #ShamsulHaque said in his comment.
Here is a good read about default scopes.
Update
If you prefer to use scopes, like #rich says, then the syntax would look like this:
scope :recent, ->(order = 'desc') { order(created_at: order.to_sym) }
Which you would have the option, in your controller, to call either asc or desc like so:
#articles = Article.recent('asc')
#articles = Article.recent('desc') # although we defaulted to 'desc', so really only need Article.recent
To explain a bit, #rich included the to_sym to convert the string 'desc' or 'asc' to a symbol like :desc or :asc. If you did not do this, you would get an error like
Direction should be :asc or :desc
Hope this helps.

Scopes
Using default_scope is a little taboo (can cause problems) - you'll be much better using a standard scope with a condition:
#app/models/article.rb
Class Article < ActiveRecord::Base
scope :recent, (order = "desc") -> { where created_at: order.to_sym}
end
This will allow you to call:
#article = Article.recent("asc")
A nice extension to #justin's answer ;)

Related

Error searching for :all

I am currently trying to create a search method. I have a database all setup; however, I am running into the errors:
ActiveRecord::RecordNotFound in ArticlesController#index
and:
Couldn't find Article with 'id'=all
Here is the pertinent code:
Articles_controller.rb
class ArticlesController < ApplicationController
def index
#articles = Article.all
#articles = Article.search(params[:id])
end
def show
#article = Article.find(params[:search])
end
def new
#article = Article.new
end
def edit
#article = Article.find(params[:id])
end
def create
#article = Article.new(params.require(:article).permit(:title, :text))
if #article.save
redirect_to #article
else
render 'new'
end
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
article.rb
class Article < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
def self.search(search)
if search
#article = Article.find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
#article = Article.find(:all)
end
end
end
index.rb
<h1>Listing articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="3"></th>
</tr>
<% #articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article_path(article),
method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
<%= form_tag articles_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
</table>
Sorry for all the code to look through. The errors I am getting are from running localhost:3000/articles, where I receive these error messages from the server. I should note that I am still very new to both Ruby and Ruby on Rails; however, I aim to learn and find seeing proper code helps me quite significantly (I am dyslexic and tend to be a visual learner).
I truly appreciate your help, thanks in advance.
I think find can not take :all. the documentation says
"Using the find method, you can retrieve the object corresponding to the specified primary key that matches any supplied options. I think this is enough
Article.where('name LIKE ?', "%#{search}%")
or if you find all the articles
Article.all
Why do you have #articles = Article.search(params[:id]) in the index method?
Also, the stack trace will tell you exactly on which line the error occurs

Searching a field in Ruby-on-Rails DB

I currently have a DB in Ruby on Rails, however, I have been having trouble with the documentation on how to do much other than list all of the items in DB. I am still new to the this language as a whole, and wish I didn't need to ask for so much help, but here it goes. My pertinent code is as follows:
migrate/(DB name)
class CreateArticles < ActiveRecord::Migration
def change
create_table :articles do |t|
t.string :title
t.text :text
t.timestamps null: false
end
end
end
articles_controller.rb
class ArticlesController < ApplicationController
def index
#articles = Article.all
Article.search(params[:id])
end
def show
#article = Article.find(params[:search])
end
def new
#article = Article.new
end
def edit
#article = Article.find(params[:id])
end
def create
#article = Article.new(params.require(:article).permit(:title, :text))
if #article.save
redirect_to #article
else
render 'new'
end
end
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
def destroy
#article = Article.find(params[:id])
#article.destroy
redirect_to articles_path
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
article.rb
class Article < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
def self.search(search)
if search
#article = Article.where('name LIKE ?', "%#{search}%")
else
#article = Article.all
end
end
end
index.html.rb
<h1>Listing articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
<tr>
<th>Title</th>
<th>Text</th>
<th colspan="3"></th>
</tr>
<% #articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.text %></td>
<td><%= link_to 'Show', article_path(article) %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article_path(article),
method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
<%= form_tag articles_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
</table>
Thanks for any help in advance!
Essentially your issue is that you're trying to set controller instance variables in a class method in your model.
class ArticlesController < ApplicationController
def index
#articles = Article.search(params[:search])
end
end
article.rb
class Article < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
def self.search(search)
if search.present?
Article.where('title LIKE ?', "%#{search}%")
else
Article.all
end
end
end
So now the class method does the search, returns the collection to the controller which assigns them to an instance variable for use in the view.
You have got your search form, made sure that is a GET one. Good. When you hit search you'll notice in your development log that there's a hit to articles#index and the browser will show the same as before. To make the search matter edit the index method in the articles controller.
def index
#articles = Article.all.search(params[:search])
end
In Article.search you have a name where you should have a title.
PS: You've got the show method a bit wrong.

Routing issue after update

So I'm still a Rails noob so I may be completely going at this wrong but I have two controllers. A Question Controller and an Answer Controller. I am trying to build a grading function that allows an admin user to assign points to essay questions. I am using the /answer/:id to be where the :id is the id of the question and then rendering a partial to iterate through all of the answers for that id. Clear as mud I'm sure...
My problem: Within the partial where the user's answer is displayed, I have a form that allows the admin to fill out the number of points for that answer and submit. Ideally, I'd like it to move to the next page (using will_paginate), but at a minimum, I'd like to stay on the same page. I am able to get the form working but it keeps going to /answers/:id but where :id is the id of the individual answer, so not what I'm hoping.
answers_controller.rb
class AnswersController < ApplicationController
def index
#user = current_user
#questions = Question.all
end
def show
#user = current_user
#question = Question.find(params[:id])
#answers = Answer.where("question_id = ?", #question.id).paginate(:page => params[:page], :per_page => 1)
#answer = Answer.where("question_id =? AND user_id = ?", #question.id, #user.id)
end
def edit
#answer = Answer.find(params[:id])
end
def update
#answer = Answer.find(params[:id])
if #answer.update_attributes(grade_params)
flash[:success] = "Answer Graded"
else
flash[:warning] = "Not Graded"
end
end
private
def grade_params
params.require(:answer).permit(:points_earned)
end
end
_essay_grades.html.erb (partial that is being rendered on the show page that contains the form)
<% #answers.each do |answer| %>
<p>User: <%= answer.user_id %></p>
<%= answer.answer %><br>
<%= #question.value %>
<br>
<%= form_for(answer) do |f| %>
<%#= f.radio_button :points_earned, #question.value %><br>
<%#= f.radio_button :points_earned, 0 %> <br>
<%= f.text_field :points_earned %> Points<br>
<br>
<%= f.submit "Award Points" %>
<% end %>
<% end %>
<br>
<br>
<%= will_paginate #answers, renderer: BootstrapPagination::Rails %>
routes.rb
Rails.application.routes.draw do
resources :admins, :answers, :static_pages, :questions
devise_for :users, :controllers => { registrations: 'registrations' },
:path => '', :path_names =>
{ :sign_in => "login", :sign_up => "register" }
root "static_pages#index"
end
I'm sure there's a simple solution here (or maybe it's changing how I have things set up...). Any help is greatly appreciated!
AFTER FEEDBACK:
Added the grades model and set up a through relationship with questions.
answer_controller.rb
class AnswersController < ApplicationController
def show
#user = current_user
#question = Question.find(params[:id])
#answers = Answer.where("question_id = ?", #question.id).paginate(:page => params[:page], :per_page => 1)
#answer = Answer.where("question_id =? AND user_id = ?", #question.id, #user.id)
end
def update
#user = current_user
#question = Question.find(params[:question_id])
#answer = #question.answers.find(params[:id])
#grade = #question.grades.new(grade_params)
if #grade.save
flash[:success] = "Answer Graded"
redirect_to #question
end
end
private
def grade_params
params.require(:grade).permit(:user_id, :answer_id, :points_earned, :graded_by, :comment)
end
end
_answer.html.erb
<%= answer.user_id %>
<%= form_tag [#question, answer], method: :put do %>
<%= hidden_field_tag :graded_by, current_user.id %>
<%= hidden_field_tag :answer_id, answer.id %>
<%= number_field_tag :points_earned %>
<%= submit_tag "Submit Grade" %>
<% end %>
routes.rb
Rails.application.routes.draw do
resources :questions do
resources :answers, only: [:update]
end
resources :admins, :static_pages
questions/show.html.erb
...
<h3>Show answers</h3>
<%= render #answers, locals: {question: #question} %>
<%= will_paginate #answers, renderer: BootstrapPagination::Rails %>
You will have to use the following in your form so the update does not load a new page, but still submits your update. Use Chrome / Firefox developer tools to view requests / responses.
<%= form_for(answer), :remote => true do |f| %>
Then, alter the update action in the answers controller to load the 'next unrated answer':
def update
rated_answer = Answer.find(params[:id])
if rated_answer.update_attributes(grade_params)
flash[:success] = "Answer Graded"
else
flash[:warning] = "Not Graded"
end
#answer = get_next_unrated_answer(rated_answer.question_id)
end
private
def get_next_unrated_answer(question_id)
# I am making a couple of assumptions on your model here, but get an answer that has not been rated yet for this question
next_answer = Answer.where("question_id = ? and points_earned = ?", question.id, nil)
#returned automatically
end
Then you will have to create app/views/answers/update.js.erb to load the new answer to your page with the following line:
$('#main_div').html('<%= escape_javascript(render partial: 'whatever_partial_you_have_created_to_display_the_next_unrated_answer') %>');
Just go and create a new partial that displays your answer and form correctly for the next unrated answer. Or ideally load your initial 'show.html.erb' with the relevant partials and reuse them.
This is the simple way to do it, but if I were you I would probably rename these new functions to not use 'update' or 'show' but rather call it something like 'rate' and even 'rate_show' so you can use update and show in its original form (for updating and answer or showing an answer) if required later in your project.
From what I understood of your question, I think you'd be best looking into nested routes:
#config/routes.rb
resources :questions do
resources :answers, only: [:update]
end
#app/controllers/questions_controller.rb
class QuestionsController < ApplicationController
def show
#question = Question.find params[:id]
end
end
#app/views/questions/show.html.erb
<%= #question.title %>
<%= render #question.answers, locals: {question: #question} %>
#app/views/questions/_answer.html.erb
<%= answer.title %>
<%= form_tag [question, answer], method: :put do %>
<%= text_field_tag :grade %>
<%= submit_tag %>
<% end %>
The above will give you what you have already (just to clarify your "clearly mad" remark is not the case at all!).
-
The following is where the nested resources come in.
At the moment, it seems you're having a problem associating an answer to a question:
where :id is the id of the individual answer, so not what I'm hoping
A remedy for this is as follows:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def update
#question = Question.find params[:question_id]
#answer = #question.answers.find params[:id]
#grade = #answer.grades.new grade_params
redirect_to #question if #grade.save
end
private
def grade_params
params.permit(:points_earned) #-> will probably have to refactor this
end
end
This will create a new grade (which you should have in an associated model), for that specific answer. Because the answer has been associated to a question, it will allow you to use the nested routes to load both.
In terms of your setup, I'd personally add a Grade model, so that you can have multiple grades per answer. This is against your current schema, but works well to ensure you have the functionality necessary to facilitate multiple grades:
#app/models/grade.rb
class Grade < ActiveRecord::Base
belongs_to :answer
belongs_to :user
end
#app/models/answer.rb
class Answer < ActiveRecord::Base
has_many :grades
end

Why do I get this NoMethodError

I'm making a twitter-copy and right now I'm trying to show all the posts from the users an other user is following. I'm new at ruby and rails, so i might be doing this a really weird way..
These are the files I have:
session#home.html.erb
<h2 class='User_Header'> Home <h2>
<%= link_to "New Post", controller: "posts", action: "new" %>
<%= link_to "Log Out", :controller => "sessions", :action => "logout" %>
<%= show_tweets("following") %>
sessions_helper
module SessionsHelper
def show_tweets(opt)
if opt == "following"
#sub = Subscription.where("userID = ?", #current_user.id)
#post = Post.where("user_id = ?", #sub.followingID)
render partial: 'shared/follower_tweets'
end
end
def show_tweet(s)
#post = Post.where("user_id = ?", s.id)
render partial: 'shared/tweet'
end
def tweet_username(p)
#username = User.where("id = ?", p.user_id)
Rails.logger.debug #username.inspect
render partial: 'shared/user'
end
end
_follower_tweets.html.erb
<h2>Tweets</h2>
<table>
<tr>
<th>Username</th>
<th>Tweet</th>
</tr>
<% div_for(#post, class: 'post') do %>
<td><%= tweet_username(#post) %></td>
<td><%= #post.content %></td>
<% end %>
</table>
_user.html.erb
<%= #username %>
session.rb
class Session < ActiveRecord::Base
attr_accessible :content, :user_id, :followingID, :userID
end
Error
app/views/sessions/home.html.erb where line #9 raised:
undefined method `followingID' for #<ActiveRecord::Relation:0x007fd74b66f8a8>
What is happening is that you have followingID on your Session model instead of Subscription model. Should be something like the following:
class Subscription < ActiveRecord::Base
attr_accessible :followingID
end
However, the problem is bigger than that. You must read about Active Record Associations, then you would be able to do something like
#subs = #current_user.subscriptions
#posts = #current_user.posts
check if your model's association is correct. the messages indicates that there's an error about this.

Follow and unfollow an article

I am trying to create an app where a user can follow or unfollow an article. To do that, I created three models, Customer, Article and Pin.
These are the relationships:
Customer
has_many articles
has_many pins
Article
has_many pins
belongs_to customer
Pins
belongs_to customer
belongs_to article
I believe a Pin must be nested within an Article. My route.rb look like this:
resources :articles do
resources :pins, :only => [:create, :destroy]
end
end
In article#index I have a form for creating or destroying the relationships:
# To create
<%= form_for [article, current_customer.pins.new] do |f| %>
<%= f.submit "Pin" %>
<% end %>
# To destroy which doesn't work because i guess you can't do the form like that
<%= form_for [article, current_customer.pins.destroy] do |f| %>
<%= f.submit "Pin" %>
<% end %>
Here are the corresponding controller actions:
def create
#article = Article.find(params[:article_id])
#pin = #article.pins.build(params[:pin])
#pin.customer = current_customer
respond_to do |format|
if #pin.save
format.html { redirect_to #pin, notice: 'Pin created' }
else
format.html { redirect_to root_url }
end
end
end
def destroy
#article = Article.find(params[:article_id])
#pin = #article.pins.find(params[:id])
#pin.destroy
respond_to do |format|
format.html { redirect_to root_url }
end
end
Now here my two questions:
How do I create a form that would delete the current relationship?
In my form I only want to show one of the buttons. How can I conditionally display the correct button?
You don't need a form to delete the relationship, links will do fine. I assume you'll be iterating through your articles in the index view -- if so, how about something like this?
<% #articles.each do |article| %>
...
<% if (pin = current_customer.pins.find_by_article(article)) %>
<%= link_to 'Unfollow', articles_pin_path(article, pin), :confirm => "Are you sure you want to unfollow this article?", :method => :delete %>
<% else %>
<%= link_to 'Follow', articles_pins_path(article), :method => :post %>
<% end %>
<% end %>
One caveat about using link_to for creating/destroying records is that if javascript is disabled, they will fall back to using GET rather than POST/DELETE. See the documentation for details.

Resources