Im making this site for clothes where I have different categories and I have a resource named "Items" for managing every clothing (since I've read somewhere, one controller per resource).
So for example, if I want to display jackets, I have a route like this:
get '/jackets', to: 'items#index', page: 'jackets'
And the controller has an index action that has a switch statement with all the different possibilities inside (I'm using scopes here):
def index
case params[:page]
# Women / Clothing
when "clothing"
#items = Item.clothing
when "beachwear"
#items = Item.beachwear
when "coats"
#items = Item.coats
Is this the right way do do it? Or should I make a single action for each kind of category that I have?
Thanks in advance and sorry for my bad english
Edit: I have something close to 100 categories.
Are Item.clothing, Item.coats, etc, scopes or? How have you implemented your model?
Why not just do something like:
def index
#items = Item.where(category: params[:category])
end
(provided, of course, that you add a category field in your Item model)
note that I changed params[:page] to params[:category] for semantic reasons
Related
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
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.
I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end
I have an api. In that api is a basecontroller that all other controllers inherit from. The basecontroller handles authentication and whether or not the API is on at all, etc.
There are users, and users can belong to a group. Users table has a group_id column.
I'm trying to introduce a new feature, whereby a select on the settings page for admin controls which users are shown from what groups. If an option is selected, only users from that group should be shown by the api.
I could go into each controller (there is one controller for each of a few different tasks - getting all users info, just active_users ids, a single users information, etc) and add the extra statement to each
if !settings.api_group.nil?
#add the additional where("group_id = ?, settings.group_id)
but that seems like a lot of repeating myself (doing it in 8 different places)
Is there some way to add something to the basecontroller that says:
if this setting option is not nil, only return user information if they are in this group
?
Thanks
You can add a method to the BaseController, and call it in each action that should have this restriction. Something like this:
in base_controller.rb:
protected
def filtered_users
if settings.api_group
User.where(:group_id => settings.group_id)
else
User.scoped
end
end
and in the controllers that inherit from it:
def index
#users = filtered_users
end
This way, you only define the filtering in one place. If it needs to change later, you only have to change it in one place. Because filtered_users actually returns a Relation, you can continue to alter the query by tacking additional .where clauses, etc, like this:
#users = filtered_users.joins(:posts).where('posts.created_at > ?', 1.week.ago)
FYI my answer was exactly what I thought it might have to be in the initial post. I'd love for there to be a more DRY solution, but I ended up doing something like this:
IN USER MODEL
def find_in_api_group
# NOTE settings.api_group is a string => "1,2,4"
if settings.api_group.nil? || settings.api_group.blank?
where("") # THERE HAS TO BE BETTER WAY OF SAYING THIS WITHOUT INTERRUPTING THE CHAIN
else
where("group_id IN (?)", settings.api_group)
end
end
IN VARIOUS CONTROLLERS
user = User.find_in_api_group
#then chain various error tests and additional activeRecord statement
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.