I’ve had a look through the form and have been unsuccessful in finding a solution for this. I currently have a page that displays a bunch of posts, with a simple #post = Post.all
I’m trying to create form that can filter by one of the columns, topics that Post have.
So basically on the left side it will have all the posts,
The right side will have all the unique topics of posts. The user can select one or more of the topics, then click submit, and the posts With the selected topics will be shown.
I’m able to show the unique topic, but unsure how to organise it so it filters after submitting. My current thought process is to create a form with all the topics. When the user presses a few topics, then submits, it filters by the selected topics. But I’m unsure on how to do the filtering in the controller as the amount of topics selected is dynamic. For example if it was just one post. It’s a simple #post = Post.where(:topic params[:chosen]) but I’m unsure how to filter it dynamically on different amounts of topics. Like if 2 or more topics are chosen.
Any help would be greatly appreciated.
You can implement the index method of controller something similar to this (Note: The code is not tested)
class PostsController < ApplicationController
before_action :construct_filters, only:[:index]
def index
#posts = Post.where(#query) # => Posts.where({"topic" => ["topic1","topic2"]}) or Post.where({}) in the case of no params passed(return all posts)
respond_to do |format|
format.html
format.js # In case of remote true submit, respond with index.js.erb and update the listing
end
end
private
# Generic method to construct query for listing
def construct_filters
#query = {}
# Pass the chosen from the form as comma separated string inside filter hash.
# Example params received: filters: {"topic" => "topic1,topic2"}
if params["filters"].present?
params["filters"].each do |k,v|
# You can modify the below line to suit your needs if you are not passing as comma seperated
filter_value = filter_value.split(',')
#query[k] = filter_value if filter_value.present?
end
end
# #query = {"topic" => ["topic1","topic2"]}
end
end
Having a generic filter method and a constructing parameter as filter hash will allow you to add more filters in future.
You can implement checkbox or multi select dropdown for selecting topics in your views, may be with input name as filters[topic]. Hope this helps!
Related
I want to include the logic from one controller action (submitting recipes a chef is allowed to cook) while also adding extra data to submit and validate (editing a chef's contact information). How would I accomplish this without duplicating all the logic?
Example controllers/actions:
ChefsController
#recipes_allowed_to_cook
cooks have long list of recipes, and here we decide 1-5 recipes of their list that they're allowed to cook at our restaurant
HiringController
#recipes_and_contact_info
Edit/submit recipes via CooksController#recipes_allowed_to_cook logic
while also adding/editing a chef's contact information
i.e. On submit, both recipes and contact info will be validted, but
recipes will be validated using same code as
CooksController#recipes_allowed_to_cook
The problem is, #recipes_allowed_to_cook has many instance variables and two different sections (one for :get and another for :post). I want to be able to use this logic simultaenously as I'm also submitting the data for a chef's contact info, so if either portion has errors we render the #recipes_and_contact_info.
You can use a Service Class:
# lib/controllers_logic/allowed_recipes_computor.rb
class ControllersLogic::AllowedRecipesComputor
attr_reader :chief, :recipes
def initialize(chief)
#chief = chief
end
def execute
#recipes = #chief.recipes.where(some_logic_to_filter_recipes)
self
end
end
Then in your controllers' actions:
def recipes_allowed_to_cook
#chief = Chief.find(params[:id])
computor = ControllersLogic::AllowedRecipesComputor.new(#chief).execute
#recipes = computor.recipes
end
I have a post controller that allows you to select a profession. I also have a work controller that has a list of professions stored seperated by commas. Eg:
Post has a row called profession, and only allows you to choose 1.
Work has a row called profession that are stored like this: business, law, accounting.
What I would like to do is once you save your post, take you to a page that shows work where Post profession is equal to Work profession. (Only relevant work show)
How would I go about doing this?
This sounds like what you're trying to do.
posts_controller.rb
def create
#post = Post.create(post_params)
if #post.save
redirect_to some_other_path(post_id: #post.id)
else
# handle error
end
end
some_controller.rb
def some_action
#post = Post.find(params[:post_id])
#works = Work.where("profession like ?", "%#{#post.profession}%")
end
Since you're using Postgres, you can use like which will match patterns within a field. This is more of a search functionality than a way of associating records. So if you have a Post that has a profession of "market", the like would match "market" and "marketing".
It might prove to be cleaner and easier to have Profession be a separate model. Post could belong_to, and Work could have_many.
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 have a resource in my project that collects some information from a user. Basically it's a form that they fill out before they can access another area of the site. It then sets a cookie for a week, but if they come back it will look up their previous entry and keep their preferences tied to them (and will update any details as long as the email address matches).
Currently I have a Applicants controller that looks like this:
class ApplicantsController < ApplicationController
...
def create
#applicant = Applicant.find_or_initialize_by_email(params[:applicant])
if #applicant.new_record? ? #applicant.save : #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
def update
if #applicant.update_attributes(params[:applicant])
set_cookie_and_redirect
else
render 'new'
end
end
end
The set_cookie_and_redirect is a private method that just sets some cookies and redirects the user to a page. The code works, but it just feels dirty. It's essentially updating a record within the create method under the condition that it's not a new record. I'm also forced to have an update method in case an existing record comes back with a validation error--the form helper will then switch the form over to sending to the update method.
So to my point... is there a more appropriate way to push the update_attributes call in the create method to the update method? Or better put, is there a better way to respect the RESTful methods in isolating the create and update functionality?
UPDATE: I wanted to be a little more specific too. If the user has filled this form out before it will set a cookie so they don't have to fill it out again for seven days. However after seven days the cookie is expired and they see the form again. The controller doesn't know if the user is new or existing until they add user input into the form which is then compared based on the email address.
Thanks in advance! I definitely look forward to anyone's thoughts on this.
The create method should only create, and the update method should only update. Let Rails decide which is going to happen based on what is inside of #applicant when the form is rendered - It essentially does what you're doing: Checks if the record is new or not, and sends it to update/create accordingly. Example:
def applicant
#applicant = Applicant.find_or_initialize_by_email(cookies[:email])
# renders applicant.html.erb form
end
<%= form_for #applicant do |f| %>
# ... fields ...
<% end %>
def create
#applicant = Applicant.new(params[:applicant])
#applicant.save
# .. etc.
end
def update
#applicant = Applicant.find_by_email(cookies[:email])
#applicant.update_attributes(params[:applicant])
# ... etc.
end
Rails will send the request to the correct action based on the new_record? status of the Applicant object.
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.