Controller syntax Ruby on Rails 6 - ruby-on-rails

Having an issue with displaying only three posts on a page. Here is the closest I've gotten to getting the controller working with a param.
def index
#Post = Post.all
#Posts = Post.find.limit('3').order('date_posted')
end
This is rendering a syntax error of sorts because it wants an ID, but I don't want to give it an ID, I want it to find the three most recent posts. How should I go about that?

Try this way
def index
#recent_posts = Post.limit(3).order(date_posted: :desc)
end
What's wrong in your code
all gives you all the objects for a model
find and find_by give you just one object which meet the id passed to find or the first one which meets the conditions passed to find_by

Related

Indexing method that gets parameters in Rails 4

I have a store application with a Product scaffold and I want to enable categories and pages that show each category of products.
My product model has a "category" attribute and I use the link_to helper to create links to each category.
In my products controller I added a method called index_by_category(cat):
def index_by_category(cat)
#products_by_category = Product.where(category: cat)
end
I'm trying to iterate #products_by_category in a view I created with the corresponding name (product/index_by_category.html.erb) just like the regular index method do. For some reason it render me the regular index method of products which shows ALL of them, even though the URL is:
http://localhost:3000/products?index_by_category=Food
This is what I did in my route.rb file:
get 'products/index_by_category'
I'm newbie to Rails development so if I did something which is wrong from the roots and the rails approach to the problem should be entirely different I also be happy to know for the sake of learning.
You are doing things a bit wrong. Try to write your controller like this:
def index_by_category
#products_by_category = Product.where(category: params[:category])
end
And update your route
get 'products/category/:category', to: 'products#index_by_category
Then visit
http://localhost:3000/products/category/Food
UPDATE
if you really want to use index method for both cases you could do that by modifying it to something like this
def index
if params[:category]
#products = Product.where(category: params[:category])
else
#products = Product.all
end
end
and then just visit
http://localhost:3000/products?category=Food

Rails 4 Sum by Model Method

In my app, I have a User model, with a goal_ytd method, which performs some calculations.
In a controller, I have a variable #users that might be User or an ActiveRecord::Relation of users, and I would like to sum all of the #users's goal_ytds.
My first inclination was:
#users.sum(&:goal_ytd)
Which threw a deprecation warning in both cases because using sum on an ActiveRecord::Relation is going away in Rails 4.1.
So, I changed the code to:
#users.to_a.sum(&:goal_ytd)
Which then threw a NoMethodError because, in a certain circumstance, #users is assigned by #users = User and User has no to_a method.
Assigning #users using #users = User.all throws a deprecation warning because Relation#all is also deprecated.
Is there a way to get all Users as an array? Is there a better way?
On Rails 4.1
If goal_ydt is a column in the users table:
#users.sum(:goal_ydt)
If goal_ydt is a method in User class:
#users.to_a.sum(&:goal_ydt)
I like to use a combination of map and sum
#users.map(&:goal_ydt).sum
You should not use enumerable methods here. Use sum which is defined on ActiveRecord::Relation and takes symbol as parameter. The main difference is that it will perform SUM query in your database, so it is much faster than pulling all the records form db. Also if any of your record has blank value for given field, enumerable sum will throw an error, while ActiveRecord's one will not. In short:
#users.sum(:goal_ydt)
EDIT:
However since goal_ydt is not a field but a method, you have no choice but to loop over the models. The way I usually do this is by using scoped method:
#users.scoped.sum(&:goal_ydt)
The issue here is rooted in a fundamental misunderstanding of the Relation#all deprecation. While Relation#all is deprecated, Model#all is not. Therefore:
#users = User.all
is still perfectly valid, while:
#users = User.where(first_name: "Mike").all
is deprecated.
So the end solution looks like:
#users = User.all
unless current_user.admin?
#users = #users.where(company_id: current_user.company_id)
end
#users.to_a.sum(&:goal_ytd)
A new question would be: How do I sum all the users goals, preferably in one line, without loading them all into memory? I suppose that's for another day.

How to display an specific item of database

My rails app has a database set.
def index
#clubs = Club.all
end
This is my controller.
If i type in my Index.html.erb
<% #clubs.each do |club| %>
<%= club.name %>
<% end %>
I get all the names of my database show in my index view.
What if I just want to pick one or just a couple?
Thru the rails console i can by typing c=Club.find(1) 1 by default takes id=1.
So how can i just display several ID's and not all one the database in the same index.html.erb.
thanks anyway!
Try this:
Let us consider that params[:ids] contains all the ids that belong to the records you want to get.
def index
#clubs = Club.where(id: params[:ids])
end
Fix
The straightforward answer here is to recommend you look at the ActiveRecord methods you can call in your controller; specifically .where:
#app/controllers/clubs_controller.rb
Class ClubsController < ApplicationController
def index
#clubs = Club.where column: "value"
end
end
This will populate the #clubs instance variable with only the records which match that particular condition. Remember, it's your Rails app, so you can do what you want with it.
Of course, it's recommended you stick with convention, but there's nothing stopping you populating specific data into your #clubs variable
--
RESTful
As someone mentioned, you shouldn't be including "filtered" records in an index action. Although I don't agree with this idea personally, the fact remains that Rails is designed to favour convention over configuration - meaning you should really leave the index action as showing all the records
You may wish to create a collection-specific action:
#config/routes.rb
resources :clubs do
collection do
get :best #-> domain.com/clubs/best
end
end
#app/controllers/clubs_controller.rb
Class ClubsController < ApplicationController
def best
#clubs = Club.where attribute: "value"
render "index"
end
end
There are several ways to select a specific record or group of records from the database. For example, you can get a single club with:
#club = Club.find(x)
where x is the id of the club. Then in your view (the .html.erb file), you can simply access the #club object's attributes.
You can also cast a wider net:
#disco_clubs = Club.where(type: "disco") # returns an ActiveRecord Relation
#disco_clubs = Club.where(type: "disco").to_a # returns an array
And then you can iterate over them in the same manner you do in your index.html.erb. Rails has a rich interface for querying the database. Check it out here.
Also note that individual records - such as those selected with the find method - are more commonly used with the show action, which is for displaying a single record. Of course, that's for generic CRUD applications. It't not a hard rule.
change
def index
#clubs = Club.all
end
to this
def index
#clubs = Club.find(insert_a_number_that_is_the_id_of_the_club_you_want)
end
Querying your database is a complex thing and gives you a ton of options so that you can get EXACTLY what you want and put it into your #clubs variable. I suggest reading this part of the rails guide
It should also be noted that if you're only going to query your database for one record then change #clubs to #club so you know what to expect.

Order column using calculation and class method

I am building an app that allows users to post. Those posts can be upvoted and downvoted. Each post record keeps track of upvotes:integer and downvotes:integer. I want to be able to order the records by which has the most upvotes total (in other words: upvotes-downvotes). I have absolutely no idea how to do this because I do not quite understand how Class methods interact with the object they are called on. This is my attempt:
My controller:
def index
#posts = Post.find(:all).most_votes.order(vote_difference)
end
My Post.rb Model:
def self.most_votes
vote_difference = (upvotes-downvotes)
end
Any ideas on how to do this?
Turns out you can actually insert the calculation right into the .order() value:
#posts = Post.find(:all).order('upvotes + downvotes')

Constructing a Rails ActiveRecord where clause

What's the best way to construct a where clause using Rails ActiveRecord? For instance, let's say I have a controller action that returns a list of blog posts:
def index
#posts = Post.all
end
Now, let's say I want to be able to pass in a url parameter so that this controller action only returns posts by a specific author:
def index
author_id = params[:author_id]
if author_id.nil?
#posts = Post.all
else
#posts = Post.where("author = ?", author_id)
end
end
This doesn't feel very DRY to me. If I were to add ordering or pagination or worse yet, more optional URL query string params to filter by, this controller action would get very complicated.
How about:
def index
author_id = params[:author_id]
#posts = Post.scoped
#post = #post.where(:author_id => author_id) if author_id.present?
#post = #post.where(:some_other_condition => some_other_value) if some_other_value.present?
end
Post.scoped is essentially a lazy loaded equivalent to Post.all (since Post.all returns an array
immediately, while Post.scoped just returns a relation object). This query won't be executed until
you actually try to iterate over it in the view (by calling .each).
Mmmh, the best approach you want to use can be to spread this in 2 actions
def index
#post = Post.all
end
def get
#post = Post.where("author=?", params[:author_id])
end
IMHO it has more sense if you think about a RESTful API, index means to list all and get (or show) to fetch the requested one and show it!
This question is pretty old but it still comes up high in google in 2019, and also some earlier answers have been deprecated, so I thought I would share a possible solution.
In the model introduce some scopes with a test for the existence of the parameter passed:
class Post
scope :where_author_ids, ->(ids){ where(author_id: ids.split(‘,’)) if ids }
scope :where_topic_ids, ->(ids){ where(topic_id: ids.split(‘,’)) if ids }
Then in the controller you can just put as many filters in as you wish e.g:
def list
#posts = Post.where_author_ids(params[:author_ids])
.where_topic_ids(params[:topic_ids])
.where_other_condition_ids(params[:other_condition_ids])
.order(:created_at)
The parameter can then be a single value or a comma separated list of values, both work fine.
If a param doesn’t exist it simply skips that where clause and doesn’t filter for that particular criteria. If the param exists but its value is an empty string then it will ‘filter out’ everything.
This solution won’t suit every circumstance of course. If you have a view page with several filters on, but upon first opening you want to show all your data instead of no data until you press a ‘submit’ button or similar (as this controller would) then you will have to tweak it slightly.
I’ve had a go at SQL injecting this and rails seems to do a good job of keeping everything secure as far as I can see.
You should model url using nested resources. The expected url would be /authors/1/posts. Think of authors as resources. Read about nested resources in this guide: http://guides.rubyonrails.org/routing.html (scroll to 2.7 - Nested Resources).
Would something like this work?
def get
raise "Bad parameters...why are you doing this?" unless params[:filter].is_a?(Hash)
#post = Post.where(params[:filter])
end
Then you can do something like:
?filter[author_id]=1&filter[post_date]=... etc.

Resources