Learning to Search in Rails - sql

I'm trying to create a search form in my rails application. I've looked up various solutions but they make little sense to me.
I'm getting the following error when I run a search through a form in my rails app. Right now my concern (other than the error) is my instance variable #computers in my index action. I'm pretty sure it's not 'the rails way' to get a search done properly and would love some advice.
Error
undefined method `%' for #<Array:0x5780460>
Parameters after Search
http://localhost:3000/computers?utf8=%E2%9C%93&direction=&sort=&search=bob
Search Form
<%= form_tag computers_path, method: "get" do %>
<%= hidden_field_tag :direction, params[:direction] %>
<%= hidden_field_tag :sort, params[:sort] %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Go", name: nil, class: "btn btn-primary" %>
<% end %>
Call to Method
def index
#computers = Computer.where(school_id: current_user.school_id).search(params[:search]).category(params[:category]).order(sort_column + " " + sort_direction)
end
Method
def Computer.search(search)
if search
search = search.downcase
params = []
values = {}
column_names.each do |c|
params << "#{c} LIKE #{c.to_sym}"
values[c.to_sym] = search
end
params.join (' OR ')
where(params,values)
else
all
end
end

You've got the right idea, but invoking the .join method does not change the object on which it is called, it merely returns a string representation. You need to store the return in a variable, something like this: paramsStr = params.join(' OR '). Then simply pass paramsStr to the where clause.
Ultimately, that is what is causing your unidentified method % for Array .... error; this version of the where method is expecting the first parameter to be a string. Check out this documentation, the part about placeholder conditions.
Hope that helps.

Related

Rails form - search engine

I try to create simple search engine but I meet some problmes. I have several search_field in my form and if either is empty should returns all objects. Otherwise when it has any content it should be selected by that content. Below is my sample form:
<%= form_for :product, url: products_path, method: :get do |form| %>
<%= form.search_field :brand %>
<%= form.search_field :model %>
<%= form.search_field :price_from %>
<%= form.search_field :price_to %>
<%= form.submit 'Submit' %>
<% end %>
my model method:
def self.search(search)
where(brand: search[:brand]).where(model: search[:model]).where("price >= ?", search[:price_from]).where("price <= ?", search[:price_to])
end
But the above piece of code is wrong because if I leave some field empty it is treated directly as empty string instead of ignore this field and final result is not correct.
Summary this form should work similarly to filter on online store
You'd could do something like this
def self.search(search)
results = all
results = results.where(brand: search[:brand]) if search[:brand]
results = results.where(model: search[:model]) if search[:model]
results = results.where("price >= ?", search[:price_from]) if search[:price_from]
results = results.where("price <= ?", search[:price_to]) if search[:price_to]
return results
end
Good luck.

undefined method `/' for nil:NilClass

I have a pretty common helper method in my application helper:
module ApplicationHelper
def quid(num)
number_to_currency (num / 100.0), unit: "£"
end
end
But my applications keeps throwing an 'undefined method `/' for nil:NilClass' error.
If I print number_to_currency (num) it doesn't have an issue, it's only when I try to put an operator like / * - + that it gives this error.
This was working fine until today, and it's only in the product index view that I get the error.
I'm not sure what's wrong, can anyone help explain? I'm stumped!
UPDATE:
Here's the product index view code:
<% #products.each do |product| %>
<h3><%= product.title %></h3>
<div id="link_text"><%= link_to "See more", product %></div>
<%= image_tag(product.avatar.url(:medium)) %><br/>
<%= product.description %><br />
<%= quid product.price_in_pence %>
<% end %>
price_in_pence is an integer, but turning 100.00 into 100 doesn't help, so I don't think that's the issue, I think it is because num is returning nil for some reason.
The error comes, because the num variable is nil in some cases. number_to_currency(nil) works, because the helper is implemented like this:
def number_to_currency(number, options = {})
return unless number
...
end
source: http://apidock.com/rails/ActionView/Helpers/NumberHelper/number_to_currency
You see, a nil doesn't hurt here, because it will instantly return.
Your function, however, tries to execute nil / 100.0 first. As the error states, the / method is not defined on nil.
You probably need some special nil treatment, or hunt the cases when nil is given to your helper.

Access a query in Ruby on Rails

I have in my controller this:
#itemsok = Search.where("first_item_id = ?", params["3"])
This is sopposed to be a query in the search table of the database asking for all the searches that have a first_item_id = 3 ...
Question 1 .- The syntax is I found it in http://guides.rubyonrails.org/active_record_querying.html but im not sure if im using it right?
Ok the question 2 is, I have this on the controller, is it ok to have querys in the controller?
In the view im printing the variable <%= #itemsok %> and all I get is a
ActiveRecord::Relation:0x007fd3d3e894d8
Any suggestions?
Thanks in advance.
ActiveRecord 3 lets you chain relations together so you can do something like this:
#itemsok = Search.where("first_item_id = ?", params["3"]).where("foo = ?", "bar")
The where() function returns an ActiveRecord::Relation. Generally this isn't a problem, since if you use the object it'll automatically run the query and return the results on the object so you'll get the database objects. AR doesn't run the query until it's actually needed.
Where will return a list of items (Array), so if you're just debugging, change your view to this:
<%= debug #itemsok.to_a %>
You seem to be constructing the query wrong way.
If you want to search for records with first_item_id = 3, you should do:
Search.where("first_item_id = ?", 3)
This will return an array of matching records, something you can't easily print with <%= #itemsok %>. You should iterate over the elements and print each one:
<% #itemsok.each do |item| %>
<%= item.name %>
<% end %>
I'd also suggest defining to_s method for the objects you want to print.
class Search
def to_s
name
end
end
Then you can simply print the object and to_s method will be automatically called for you:
<% #itemsok.each do |item| %>
<%= item %>
<% end %>
The right way to do is to define a namedscope in the model and then use it in the controller.
Something similar to this :
class Search < ActiveRecord::Base
named_scope:item_ok,lambda {|*args|{:conditions=>["item_id >= ?", args.first]}}
end
and then call the namedscope from the controller like this :
#itemsok = Search.item_ok(params[:value])

Rails filtering with acts_as_taggable gem

I am working the acts-as-taggable-on gem and have a question about how to filter down search results based on tags users select. Here's an abridged look at my controller:
class PhotosController < ApplicationController
def index
#photos = Photo.where(["created_at > ? AND is_approved = ?", 1.months.ago, true])
#tags = ["Animals", "Architecture", "Cars", "Flowers", "Food/Drink", "General", "Landscape", "Mountains", "Nature"]
end
def search_by_tag(tag)
#photos = Photo.where('tagged_with LIKE ?', tag)
end
end
Photos/index
<% #tags.each do |tag| %>
<%= link_to tag, {:search_by_tag => tag}, :class => "tag" %>
<% end %>
This lists out all of the tags from the hash #tags defined in index, but clicking them doesn't actually filter anything down. Here's a look at what clicking one of those links produces in the log:
Started GET "/photos?search_by_tag=Animals" for 127.0.0.1 at Sun Oct 09 17:11:09 -0400 2011
Processing by PhotosController#index as HTML
Parameters: {"search_by_tag"=>"Animals"}
SQL (0.5ms) SELECT name FROM sqlite_master WHERE type = 'table' AND NOT name = 'sqlite_sequence'
The result I want is for the page to display only Photos that are tagged_with whichever tag was clicked on. I can't quite figure out how to accomplish this.
(Side-question: I can't seem to find a way to list out all of the tags from the tags table that acts-as-taggable-on generated. Doing something like Photo.tagged_with or Photo.tags doesn't work. I am able to see the "tags" table the gem created, and the entries inside of it; I'm just not really clear how to handle that using this gem)
Any thoughts greatly appreciated.
UPDATE
I've updated my code and am a bit closer.
class PhotosController < ApplicationController
def search_by_tag
#photos = Photo.tagged_with(params[:tag_name])
end
photos/index
<% Photo.tag_counts_on(:tags).map(&:name).each do |tag| %>
<%= link_to tag, {:action => 'search_by_tag', :tag_name => tag}, :class => "tag" %>
<% end %>
I believe this is closer, but still working through this...
You have a number of errors in your code:
Your link_to call is actually calling the index action.
Your search_by_tag method is expecting an argument, where it should be using the params hash to access the parameters passed to it in the web request.
tagged_with is a class method added by acts_as_taggable_on, not a field in your table - therefore you can't use it in the where method like you have done.
Finally, to get all the tag names: Photo.tag_counts_on(:tags_or_whatever_you_tagged_on).map(&:name)
Take a look at the acts_as_taggable_on documentation and you'll see examples of how to use tag_counts_on and tagged_with.
As for the Rails things: http://guides.rubyonrails.org/ http://railsforzombies.org/ and/or http://railscasts.com/

Rails 3 custom validation: Highlighting offending fields

I'm writing my first custom rails validation, and would like to tag the offending class with an html "error" class if they return false - I can't quite figure out how to do it. Relevant validation code below - any help appreciated.
(If it makes a difference, I'm using jQuery)
validates_each :shop do |record, attr, value|
shopvar = record.shops.map{ |s| s.email.downcase.strip }
if shopvar.count != shopvar.uniq.count
record.errors.add(attr, 'has the same email address entered more than once')
#record.errors[attr] << "You have entered this shop in the form twice"
end
end
So in your form you'd have something like this for an input field
<%= form.text_field :title %>
Since errors is a hash you could use the "include?" method like so...
errors.include?(:title)
This tells you that there's something wrong with this field. Now all you need to do is style it.
Whack on a ternary operator asi...
<% css_class = errors.include?(:title) ? "highlight_error_class" : "no_problem_class" %>
<%= form.text_field :title, :class => css_class %>
Done.