How to require a value is entered in a search form - ruby-on-rails

I built a basic search form that queries one column in one table of my app. I followed episode 37 Railscast: http://railscasts.com/episodes/37-simple-search-form. Note I just posted another search related question, but it's on a completely different issue.
In my app, the search queries the zip code column of my profile table, and returns a list of profiles that contain the right zip code.
Here's my problem. Currently, when a user leaves the input blank and hits the submit button, the search displays all profiles on the site. I don't want this to happen. If the field is blank, I don't want the search to go through. I'd like to either do a flash notice or throw an error, explaining that the user needs to enter a zip code to proceed.
Here's my setup:
PROFILES CONTROLLER
def index
#profiles = Profile.search(params[:search])
end
PROFILE MODEL
def self.search(search)
if search
find(:all, :conditions => ['zip LIKE ?', "%#{search}%"])
else
find(:all)
end
end
PROFILE/INDEX.HTML.ERB
<% form_tag ('/profiles', :method => :get) do %>
<%= text_field_tag :search, params[:search], :maxlength => 5 %>
<%= submit_tag "Go", :name => nil %>
<% end %>
Thanks!

def index
#profiles = Profile.search(params[:search]) unless params[:search].blank?
end
You probably don't want to throw an error if the search field is blank, because the user will see that error the first time he comes to the index page. To properly handle that type of error message, you'll need to do one of several things.
Split the form generation and the actual search into two separate actions. In a RESTful app, this would typically be a new and create action. (new for the form, create for the actual search).
Add a check for a post, as opposed to a get. Only attempt the search, or throw the error, if it's a post. Otherwise, just show the form. You'll typically see this in older Rails examples (like pre- 2.0 tutorials).
Add some hidden field that says "Hey, I'm submitting a search." This is the same idea as checking for a post, but would still work if you wanted all gets for some reason.
My choice would be the first one. It'd roughly look like this.
def new
end
def create
if params[:search].blank?
flash.now[:error] = "Please enter a zip code to search for."
render :new
else
#profiles = Profile.search(params[:search])
render :show
end
end
In your views, new.html.erb (or .haml or whatever) would contain your search form and show.html.erb would contain your search results. Usually there's a search form partial that both of them would share.

You just need to check if it's blank before you do your search.
def index
if params[:search].blank?
flash[:error] = "Doh! You forgot the zip code."
else
#profiles = Profile.search(params[:search])
end
end
If returning all results is never a use case then you might want to remove that code from your model as well. Also if you're checking for a blank search in more than this one controller action you should move the logic into the model.

I actually found an answer to this that doesn't require me to make any changes to my current setup.
If you look in the search model above, which I copied from the Railscast, you see that Ryan included wildcards on both sides of the search query:
find(:all, :conditions => ['zip LIKE ?', "%#{search}%"])
I'm not familiar with sql syntax at all, so I missed that. It was those wildcards that was letting through the blank search and returning all results in the database. When I removed the "%'s" from that sql statement, the blank search now returns the same result as a search where we don't have a record matching the zip queried, which is the result I wanted.
Just wanted to share so others might catch this in the future.

Related

How to compare two items within Ruby on Rails?

So I'm trying to re-create GitHub version control for let's say posts. I've found a way to re-create an original post using duplicate AND another method to create a new post based on the original. Cool.
My issue is being able to display both the original and the new on the same page.
What I've attempted thus far is to just rely on the show method with having:
def show
#post = Post.find(params[:id])
end
Then in the view have in the form a checkbox to allow a user to select multiple posts, click a submit, and a new page renders displaying both side by side. Preferably showing the differences between the two but that's a wish list as I deal with this first.
Actually could I just simply do?:
def other_show
#post = Post.where(params[:id])
end
I also added in status as a boolean to help on the view for marking the checkbox. Would I then need to put something in the other_show method about the status?
If you want to "recreate" some sort of version control I suggest you use something like the audited. Instead of building your own. From your example and comments it seems you don't have a clear relation between all related (versions of) posts.
Using this gem, each change to the Post content (for example, if configured properly) would be stored as an audit.
Showing the differences is a different problem. That's usually called a diff and you can find gems that do it for you, for example: diffy
To show 2 different entities on one page you need to give posts_controller both ids.
Declare your show method like this:
def show
#original = Post.find(params[:id])
#compared = Post.find(params[:compared_id])
end
Correct route to this method will look like this:
/posts/:id?compared_id=:another_id
# Example: /posts/1?compared_id=2
To construct such a link in your view, you need to declare link_to method like this:
<%= link_to '1 <> 2', post_path(#post, compared_id: '2') %>
If you want to have a page where user can check 2 checkboxes for certain posts, you'll need to construct such href via Javascript.
But in fact I wouldn't suggest you to modify show method for such a task. It is better to use show method only for showing one entity from database. You can create another method, e.g. compare and pass both parameters there.
def compare
#original = Post.find(params[:original_id])
#compared = Post.find(params[:compared_id])
end
In routes.rb
resources :posts do
get 'compare', on: :collection
end
It will give you helper compare_posts_path, which will lead to /posts/compare and you'll need to pass original_id and compared_id to it, like this:
<%= link_to 'Compare', compare_posts_path(original_id: 'some_id', compared_id: 'some_another_id') %>
It will result to
/posts/compare?original_id=some_id&compared_id=some_another_id

create edit URL route that doesn't show :id

I'm in the midst of trying to clean up my routing. I have a company model that can log in and create applications. They can create several.
Currently this is my setup:
Routes
get 'applications/edit/:id', to: 'applications#edit'
Applications_controller
def edit
#application = current_company.applications.find(params[:id])
end
def update
#application = Application.find(params[:id])
if #application.update(application_params)
redirect_to dashboard_path
else
render 'edit'
end
end
Each company have their own dashboard. Here's my code from
/dashboard
Your active applications
<% #applications.all.each do |f| %>
<%= link_to "Application", show_path + "/#{f.id}" %> | <%= link_to "Edit", edit_application_path("#{f.id}") %>
<br>
<% end %>
Now this all works, if I go to edit_application/11 f.ex I see it.
The thing I'd like changed is to remove the :id from the URL.
Thus make it more secure and to give a nicer feel. Now it took me 5 minutes before I realised I could just change the :id url and edit everything. Thus I added the current_company.applications to stop that. Yet I don't feel like this is very secure.
If you want to remove the :id, you'll still need a way to find the data you want.
As long as you have the url /edit/12 and as long as you use the id 12 in the GET url to find your content, it will show in the browser bar. The only way to "hide" it (but it's not more secure at all, because it's easily found out), is to use a POST request with a form containing a hidden field with the id (can be made in JavaScript).
You are asking the application to get the id from the link in the #applications.all.each but the only way it can do that is to include it somewhere in the request (be it GET, POST, COOKIES/SESSION, ...).
For another (possibly better) solution, read on.
A very common practice is to use slugs: you create a unique key for each content, for example, if your title is "My great app", the slug will be my-great-app. Thus there is no id in your URL (and it cannot be found out if you always use slugs as references). The advantage is that you'll still find a quick match for what you're searching for (creating an unique index on the slugs).
Some further reading about slugs:
http://rubysnippets.com/2013/02/04/rails-seo-pretty-urls-in-rails/
What is the etymology of 'slug'?

ActiveAdmin batch_action template error and no update

My ActiveAdmin registered model has an "active" boolean field. I want to include a batch action to "activate" multiple records at once.
I am trying to follow the instructions at:
http://activeadmin.info/docs/9-batch-actions.html
for doing a custom batch action and I am having two problems.
I have this:
ActiveAdmin.register Venue do
batch_action :deactivate do |selection|
Venue.find(selection).each do |v|
v.active = false
end
end
end
When I try to activate something I get a template not found error. It is looking for a "batch_action" template. I didn't see anything in that doc about needing to also add a template. If I add a template with that name the error goes away and it displays the template...this is of course not what I want. I want it to just redisplay the index.
In either case (with or without a template in place), the model is not being updated. I can see in the log where it just does a select for the selected records and does nothing else.
I got rid of the issues by doing the following:
batch_action :activate do |selection|
Venue.find(selection).each do |v|
v.active = true
v.save
end
redirect_to :back #this ensures any current filter stays active
end
The 'save' part seems obvious but the example in the docs threw me off on my first attempt. It seems like this would be a more relevant example for the docs.

Rails3 Admin UI frameworks

We will soon be rewriting a 5 year old rails app, with a very unsound code foundation, from scratch in a brand new Rails 3 app with all the new hotness. The current app has a substantial custom admin UI backend which depends on now admin frameworks at all. Just some base controller classes and some somewhat useful CSS conventions. But maintaining that UI is a lot of work, especially if we want it to look half way nice.
So I'm in the market for an Admin UI framework that will make the simple stuff trivial, but without getting the way of more complex customization in both form and function.
The top contender, ActiveAdmin, seems to be very popular, and after playing with it a bit I have some concerns. It seems to declare a whole unique DSL that exists in a single ruby file. This is kind of neat, but it's also a completely different than how most other Rails apps are architected. It abstracts away the view, the helper the controller, and gives you a pure ruby DSL. It seems to me that this would get in the way of doing tricky things, more advanced custom things in our admin views. DSL's are great, until you want to do something they don't explicitly support.
Example "Resource" from my experimentation, with no controller and no view.
ActiveAdmin.register Region do
menu parent: "Wines"
show title: :name
index do
column(:zone) { |o| link_to o.zone, admin_region_path(o) }
column(:name) { |o| link_to o.name, admin_region_path(o) }
default_actions
end
end
So, the questions:
Is not being based on the standard Rails MVC architecture in separate files, and the typical controller inheritance based implementation of an admin area, actually something I should be concerned about? Will it hamper extensibility in the long term?
Is the DSL in ActiveAdmin better and more flexible than I'm giving it credit for?
Should I be looking at some other framework that lends itself better to my goals of high customization?
Should I stop being lazy and roll my own?
Does the choice of Mongoid instead of MySQL as a database affect any of the above questions?
It is worth mentioning that in ActiveAdmin you can also create your own complex forms by using partials
form partial: 'my_form'
and expand controller functions with a controller block.
controller do
def edit
end
def index
end
end
+1 for active admin, I use it in many projects, including a cms I'm building. It indeed is more flexible than many people who are newer with it give it credit for, at the end of the day you can always do:
controller do
def custom_action
Puts "hi"
end
end
(Think that's the right syntax writing from phone so all this is off top of head)
Also, I swear by inherited resources, which active admin controllers extend, as they really force you (in a good way) into writing restful, reuse able code. Bottom line is I believe active admin is leaps and bounds ahead of the others I've tried (railsadmin and at least one other)
Update:
Sure, here is the inherited_resources documentation
https://github.com/josevalim/inherited_resources
And here is an example of modifying the controller directly, from my little CMS project.
ActiveAdmin.register Channel do
index do
column :title
column :slug
column "Fields" do |channel|
"#{link_to('View Fields', admin_channel_entry_fields_path(channel))} #{link_to 'Add Field', new_admin_channel_entry_field_path(channel)}".html_safe
end
column "Actions" do |channel|
"#{link_to('View Entries', admin_channel_entries_path(channel))} #{link_to 'Create Entry', new_admin_channel_entry_path(channel)}".html_safe
end
default_actions
end
form :html => { :enctype => "multipart/form-data" } do |f|
f.inputs "Channel" do
f.input :title
f.input :display_name
f.input :image
end
f.buttons
end
controller do
def begin_of_association_chain
current_site
end
def tags
query = params[:q]
if query[-1,1] == " "
query = query.gsub(" ", "")
ActsAsTaggableOn::Tag.find_or_create_by_name(query)
end
#tags = ActsAsTaggableOn::Tag.all
#tags = #tags.select { |v| v.name =~ /#{query}/i }
respond_to do |format|
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name }}}
end
end
end
end
Basically, I am using the inherited resources, begin_of_association_chain method (one of my favorite things about IR), to scope all the data within channels, or any of the admin resources that inherit from my channels resource, to the current site, without having a url like /admin/sites/1/channels -- Because I am already setting current_site based on the url the visitor enters with. -- Anyways, basically once you are inside:
controller do
puts self.inspect
end
Returns the actual controller itself, e.g. Admin::ChannelsController (which < InheritedResources::Base, maybe not directly but all the IH controller methods should be available at this point).

DRYing up routes and javascript responses

I'm not sure if I'm missing a known design pattern, but I keep coming up against the following problem with RESTful routes Rails.
In my example, I have a users controller that can respond in javascript (:js) format. The default response populates a page element with a list of the paginated users:
# /app/controllers/users_controller.rb
class UsersController < ActionController
def index
#users = User.paginate(:all, :page => params[:page], :conditions => ['name ILIKE ?', params[:name])
respond_to do |format|
format.html
format.js
end
end
end
The corresponding RJS template would look like:
# /app/views/users/index.js.rjs
page.replace_html :users, :partial => 'users'
This works fine, allowing me to perform AJAX lookups on users. However, in another part of my site (say the user editing form) I would like to perform an AJAX lookup of users, but update a set of ''select'' options or perform an inline autocomplete, rather than update the #users page element, e.g.
# /app/views/users/edit.html.erb
<%= f.text_field :name %>
$('#user_name').autocomplete({url: '/users', data: 'name=value', ...})
My question is what would be the best DRY way to achieve this? I don't think I should need to create a new controller action to correspond to the different view, as this would involve repeating the finder code. The only solution I've come across so far is to build some javascript conditions into my RJS helper:
# /app/views/users/index.js.rjs
page << "if($('#users').length > 0)"
page.replace_html :users, :partial => 'users'
page << "else"
page.replace_html :user_options, :partial => 'user_options_for_select'
This feels very brittle, and unclean for Rails. Am I missing something in how I can respond with different views depending on the calling controller?
Appreciate any help!
Chris
In one of them you have a list of users and the other one a list of options.
So even though for now, your two pages are having the same feature, they're independent from each other and you might want to change things for only one of them in the future.
So I'd keep them distinct with two different javascript actions . It'll will allow you to much more easily make them evoluate on their different path.
Anyway as you can see, they're already quite different. You have two different partials and two different html tags id.
Trying to have the same code for them both here seems quite confusing to me.
So yes I'd create two actions, one for the users list and one for their options.

Resources