How to make optional :conditions for a find - ruby-on-rails

Hello I have the followong struggle in my head. I want a text-field in which the use can type in some parameters, which will be used as filter-criteria for the :conditions hash in my find method.
I have created a helper, with takes an option and merge the hash to the options:
In my controller:
#bills = adminbill_filter(:limit=>params[:limit] || 50,:offset=>params[:offset] || 0, :conditions=>params[:options])
In my helper:
def link_to_with_current(text, link, condition, *args)
options = args.first || {}
options[:class] = condition ? 'current' : nil
link_to text, link, options
end
In my view:
<%= text_field :filter ,:criteria, :class=>'roundRect',:id=>'name', :value=>12009%>
<%= button_to_with_filter 'Start Filter', 'index', :filter_condition=>true, :options=>{:id=>81}%>
Is it somehow possible to pass the value of text_field into the :option=>{...} of the button_to_with_filter? I find this solution (if it is working) quite unhandy. Your comments are as always very helpful.
Greetings
Matthias

It seems kind of terrifying to put in the contents of user-submitted params without vetting them in any capacity. You're probably going to run into all kinds of exceptions if the data doesn't come in as expected, or is formulated to be malicious.
I've found it's often easier to use a chained scopes approach:
def index
bills_scope = Bill
# Use an example Bill.with_id scope
if (params[:with_id])
bills_scope = bills_scope.with_id(params[:with_id])
end
# Repeat as required
# Finally, use the scope to retrieve matching records
#bills = bills_scope.paginated
end
Using something like will_paginate can help with your offset and limit values.

If the text field and button were encapsulated in a form, and the button was the submit button, the text field's value would automatically be brought into the params hash. Then you wouldn't have to deal with it. I can't recall at the moment the exact Rails helpers that will do this for you, but you want the resulting form to probably be something like this:
<% form_for :options, :url => {:action => :index}, :html => { :method => :get } do |f| %>
<%= f.text_field :filter ,:criteria, :class=>'roundRect',:id=>'name', :value=>12009%>
<%= f.submit 'Start Filter' %>
<% end %>
Which may change some, since I don't know the underlying code behind your methods.
Otherwise, the only thing I can think of is using a Javascript event on the button that grabs the value of the text field before it submits.

Thanks for your help, I came across named_scope and solved the problem with the following code:
Bill model:
class Bill < ActiveRecord::Base
# named_scope werden fuer Filterfunktionen bei Adminbill benoetigt
named_scope :standard, :order => "created_at DESC"
named_scope :limit, lambda {|*args| {:limit=>(args.first)|| 50}}
named_scope :offset, lambda {|*args| {:offset=>(args.first || 10)}}
named_scope :paid, :conditions=>"paid IS NOT NULL"
named_scope :not_paid, :conditions=>{:paid=>nil}
named_scope :test_bill, :conditions => {:test_bill=>true}
named_scope :no_test_bill, :conditions => {:test_bill=>false}
named_scope :find_via_bill_id, lambda {|*args|{:conditions=>{:id=>(args.first || 210)}}}
named_scope :find_via_email, lambda {|*args| {:conditions=>{:buyer_id=>args.first}}}
controller:
def index
logger.debug "The object is #{current_user}"
if params[:filterInput] != nil && !params[:filterInput].empty?
filter_array = params[:filterInput].split('&')
bill_scope = Bill.scoped({})
bill_scope = bill_scope.standard
# Filtere via Regexp-Matching die Zahlen der Eingabe heraus
filter_array.each do |el|
if el =~ /limit\([0-9]+\)/
number =
bill_scope = bill_scope.limit(el.scan(/\d+/)[0])
elsif el =~ /offset\([0-9]+\)/
bill_scope = bill_scope.offset(el.scan(/\d+/)[0])
elsif el == 'paid'
bill_scope = bill_scope.paid
elsif el == 'not_paid'
bill_scope = bill_scope.not_paid
elsif el == 'test_bill'
bill_scope = bill_scope.test_bill
elsif el =~ /find_via_bill_id\([0-9]+\)/
bill_scope = bill_scope.find_via_bill_id(el.scan(/\d+/)[0])
elsif el =~ /find_via_email\([A-Za-z0-9.#-]+\)/
email = el.scan(/\([A-Za-z0-9.#-]+\)/)[0]
# TODO geht bestimmt auch eleganter durch besseres Matching
email = email.gsub("(", "")
email = email.gsub(")", "")
user = User.find_by_email(email) unless User.find_by_email(email).blank?
bill_scope = bill_scope.find_via_email(user.id)
end
end
#bills = bill_scope
else
#bills = Bill.standard.limit.offset
end
And in the view:
<% form_tag(:action => 'index') do %>
<%= text_field_tag 'filterInput', nil, :size => 40 %>
<%= submit_tag 'Start Filter'%>
<% end %>
Now you can pass in the tex-field e.g.the following valid expression: paid&limits(20)
I know that the controller solution isn't very elegant but for me it was the fastest way to solve this problem.

Related

Activerecord-reputation-system - how to check value of current_user's vote

I'm checking if a user has voted on a given post and then changing the up and down arrows' styling. The activerecord-reputation-system gem hasn't been updated in a while and every example I find either uses outdated code or requires tracking "up" votes and "down" votes separately. I prefer to just use :post_votes and check the value of that one parameter.
posts_controller.rb
#voted_items = Post.evaluated_by(:post_votes, current_user)
index.html.erb
<% if current_user && #voted_items.include?(post) %>
# display active up or down arrows
<% end %>
How do I get just the value of the relevant record within the #voted_items object?
Edit: I notice there is no built-in scope for :value. Maybe adding one manually would let me query a particular value?
For now I am came up with this method def on the model:
def evaluation_value(user, comment)
if #up_voted = ReputationSystem::Evaluation.where(:reputation_name => "comment_votes",
:value => "1.0", :source_id => user.id, :source_type => user.class.name,
:target_id => comment.id, :target_type => comment.class.name).exists?
"upvoted"
elsif #down_voted = ReputationSystem::Evaluation.where(:reputation_name => "comment_votes",
:value => "-1.0", :source_id => user.id, :source_type => user.class.name,
:target_id => comment.id, :target_type => comment.class.name).exists?
"downvoted"
else
nil
end
end
It's not ideal because it requires hardcoding the values and other details, but it works for now until the gem is updated to allow checking individual values.
This way I can run comment.evaluation_for(current_user, comment) == "upvoted" or downvoted.

Heroku case sensitivity

I have recently uploaded my application to heroku and due to the postgresql, the case sensitivity does not work, I am wondering how will I fix this? My code is as followed:
relation = Game.gamsearch(params[:gamsearch])
relation = Game.consearch(params[:consearch]) if params[:consearch].present?
relation = Game.gensearch(params[:gensearch]) if params[:gensearch].present?
relation = Game.where("game_name LIKE ?", "#{params[:game_name]}%") if params[:game_name].present?
relation = Game.where("console = ?", params[:console]) if params[:console].present?
view code:
<%= form_tag games_path, :controller => 'games', :action => 'gamsearch', :method => 'get' do %>
<%= text_field_tag :gamsearch, params[:gamsearch] %>
<%= submit_tag t('.searchb'), :game_name => nil %>
<% end %>
This is the model code:
def self.gensearch(*args)
#search for games by their genre
return [] if args.blank?
cond_text, cond_values = [], []
args.each do |str|
next if str.blank?
cond_text << "( %s )" % str.split.map{|w| "genre LIKE ? "}.join(" OR ")
cond_values.concat(str.split.map{|w| "%#{w}%"})
end
all :conditions => [cond_text.join(" AND "), *cond_values]
end
relation = Game.where("game_name ILIKE ?", "#{params[:game_name]}%") if params[:game_name].present?
notice the ILIKE instead of LIKE
ILIKE is the way to go for PG, but if you have another database (sqlite) in development, that might not work. One way to get around this is to make both values uppercase and then compare the uppercased version of both.

Passing parameter back to Model to refine Random action

I'm creating an application that'll display a random picture based upon a defined letter in a word.
Images are attached to a Pictures model (containing another "letter" field) using Paperclip, and will be iterated through in an each block.
How would I go about passing the letter back from the each block to the model for random selection.
This is what I've come up with so far, but it's throwing the following error.
undefined method `%' for {:letter=>"e"}:Hash
Model:
def self.random(letter)
if (c = count) != 0
find(:first, :conditions => [:letter => letter], :offset =>rand(c))
end
end
View:
<% #letters.each do |a| %>
<%= Picture.random(a).image(:thumb) %>
<% end %>
Thanks
One problem is your conditions has a syntax error. The hash notation is wrong:
:conditions => [:letter => letter]
should be
:conditions => {:letter => letter}
Also, it seems to me that your random scope will always exclude the first Picture if you don't allow an offset of 0. Besides that, do you really want to return nil if the random number was 0?
Picture.random(a).image(:thumb) would throw "undefined method 'image' for nil:NilClass" exception every time c==0. Can probably just use:
def self.random(letter)
find(:first, :conditions => {:letter => letter}, :offset =>rand(count))
end
EDIT: You'll either need to guarantee that your db has images for all letters, or tell the user no image exists for a given letter.
<% #letters.each do |a| %>
<% if pic = Picture.random(a).image(:thumb) %>
<%= pic.image(:thumb) %>
<% else %>
No image available for <%= a %>
<% end %>
<% end %>
Or the like...
EDIT: Actually I don't think your offset strategy will work. One other approach would be to return the set of images available for the given letter and randomly select from that collection, something like:
def self.random(letter)
pics = find(:all, :conditions => {:letter => letter})
pics[rand(pics.size)] if !pics.blank?
end

Rails: Preserving GET query string parameters in link_to

I have a typical search facility in my app which returns a list of results that can be paginated, sorted, viewed with a different records_per_page value, etc. Each of these options is controlled by parameters in the query string. A simplified example:
/search?q=test&page=2
Now say I need to display a set of links that set records_per_page value to 10, 20, 30. Each link must include the existing query parameters, which can be a very long set, plus a new per_page parameter.
/search?q=test&page=2& ... &per_page=10
/search?q=test&page=2& ... &per_page=20
/search?q=test&page=2& ... &per_page=30
Is there an easy way to do it with just link_to helper or I need to parse and reproduce the query string from previous request somehow?
link_to 'Link', request.query_parameters.merge({:per_page => 20})
link_to 'Link', params.merge({:per_page => 20})
The simplest way to merge the new params with the query parameters and NOT with all parameters (including those obtained through the path) is to merge with request.query_parameters
link_to 'Search', search_path(request.query_parameters.merge({ per_page: 20 }))
Otherwise you end up with query strings duplicating the path parameters, for example ?action=index&controller=products&foo=bar instead of ?foo=bar.
If you want to keep existing params and not expose yourself to XSS attacks, be sure to clean the params hash, leaving only the params that your app can be sending:
# inline
<%= link_to 'Link', params.slice(:sort).merge(per_page: 20) %>
If you use it in multiple places, clean the params in the controller:
# your_controller.rb
#params = params.slice(:sort, :per_page)
# view
<%= link_to 'Link', #params.merge(per_page: 20) %>
You can just throw elements of the params hash at link_to. Like
link_to "some_other_link", "/search", :page => params[:page]
This works if the links you are processing aren't given to you by request.params.
require 'rack/utils'
require 'uri'
def modify_query url, options={}
uri = URI(url)
query_hash = Rack::Utils.parse_query(uri.query)
query_hash.merge!(options)
uri.query = Rack::Utils.build_query(query_hash)
uri.to_s
end
puts modify_query('/search?q=test&page=2&per_page=10', 'per_page' => 20)
puts modify_query('/search?q=test&page=2', 'per_page' => 30)
# Outputs
# /search?q=test&page=2&per_page=20
# /search?q=test&page=2&per_page=30
What about
<%= link_to 'Whatever', :overwrite_params => { :pear_page => 20 } %>
?
A bit late i know..
If your using this as a way to filter search results have a look at my helper :)
This automagicly removes all blank and unneeded params and add the class "selected" if all of it's new params were already set.
def search_to s, args={}
selected = 0
args.each do |k, v|
selected = selected + 1 if params[k] == v.to_s || ( params[k].nil? && v.blank? )
end
if #search_params_base.nil?
#search_params_base = request.parameters.clone
#search_params_base.delete(:action)
#search_params_base.delete(:controller)
#search_params_base.delete(:page)
#search_params_base.delete_if{|k, v| v.nil? || v.blank?}
#search_params_base.delete(:utf8) if #search_params_base[:keywords].nil?
end
search_params = #search_params_base.merge(args)
search_params.delete_if{|k, v| v.nil? || v.blank?}
link_to s, search_path + '?' + search_params.to_param, :class => selected == args.length ? 'selected' : nil
end
You can then just use this in your view:
search_to '$80 to $110', :price => 80..110
Or in your case:
search_to '30 per page', :page => params[:page], :per_page => 30

Searching in Ruby on Rails - How do I search on each word entered and not the exact string?

I have built a blog application w/ ruby on rails and I am trying to implement a search feature. The blog application allows for users to tag posts. The tags are created in their own table and belong_to :post. When a tag is created, so is a record in the tag table where the name of the tag is tag_name and associated by post_id. Tags are strings.
I am trying to allow a user to search for any word tag_name in any order. Here is what I mean. Lets say a particular post has a tag that is 'ruby code controller'. In my current search feature, that tag will be found if the user searches for 'ruby', 'ruby code', or 'ruby code controller'. It will not be found if the user types in 'ruby controller'.
Essentially what I am saying is that I would like each word entered in the search to be searched for, not necessarily the 'string' that is entered into the search.
I have been experimenting with providing multiple textfields to allow the user to type in multiple words, and also have been playing around with the code below, but can't seem to accomplish the above. I am new to ruby and rails so sorry if this is an obvious question and prior to installing a gem or plugin I thought I would check to see if there was a simple fix. Here is my code:
View: /views/tags/index.html.erb
<% form_tag tags_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search], :class => "textfield-search" %>
<%= submit_tag "Search", :name => nil, :class => "search-button" %>
</p>
<% end %>
TagsController
def index
#tags = Tag.search(params[:search]).paginate :page => params[:page], :per_page => 5
#tagsearch = Tag.search(params[:search])
#tag_counts = Tag.count(:group => :tag_name,
:order => 'count_all DESC', :limit => 100)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tags }
end
end
Tag Model
class Tag < ActiveRecord::Base
belongs_to :post
validates_length_of :tag_name, :maximum=>42
validates_presence_of :tag_name
def self.search(search)
if search
find(:all, :order => "created_at DESC", :conditions => ['tag_name LIKE ?', "%#{search}%"])
else
find(:all, :order => "created_at DESC")
end
end
end
If I read your problem correctly, you want to return a row if the tag names for the row matches one of the words passed in the query string.
You can rewrite your search method as follows:
def self.search(search)
all :conditions => (search ? { :tag_name => search.split} : [])
end
If you need partial matching then do the following:
def self.search(str)
return [] if str.blank?
cond_text = str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values = str.split.map{|w| "%#{w}%"}
all(:conditions => (str ? [cond_text, *cond_values] : []))
end
Edit 1
If you want pass multiple search strings then:
def self.search(*args)
return [] if args.blank?
cond_text, cond_values = [], []
args.each do |str|
next if str.blank?
cond_text << "( %s )" % str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values.concat(str.split.map{|w| "%#{w}%"})
end
all :conditions => [cond_text.join(" AND "), *cond_values]
end
Now you can make calls such as:
Tag.search("Ruby On Rails")
Tag.search("Ruby On Rails", "Houston")
Tag.search("Ruby On Rails", "Houston", "TX")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah", ....) # n parameters
Caveat:
The wild card LIKE searches are not very efficient(as they don't use the index). You should consider using Sphinx (via ThinkingSphinx) OR Solr(via SunSpot) if you have lot of data.
You can try to set up ferret, or if you are really bend on just using rails, try this:
# Break the search string into words
words = params[:search].blank? ? [] : params[:search].split(' ')
conditions = [[]] # Why this way? You'll know soon
words.each do |word|
conditions[0] << ["tag_name LIKE ?"]
conditions << "%#{word}%"
end
conditions[0] = conditions.first.join(" OR ") # Converts condition string to include " OR " easily ;-)
# Proceed to find using `:conditions => conditions` in your find
hope this helps =)
Sounds like you need a full text search. The best search integration right now is with Sphinx and the Thinking_Sphinx plugin. I have used it on several projects and it's super easy to setup.
You do need to install sphinx on your host so if you are using a shared host that could present some issues.
You could also use full text search in a MyISAM MySQL database, but performance on that is pretty poor.
Once you have your sphinx installed you just put what you want to index in your model and call model.search. The results will be a list of model objects. It supports will_paginate as well.
I'd suggest looking at Searchlogic if you don't want to use a separate fulltext search engine (Ferret, Sphinx, etc). It makes simple searches extremely easy, although you may not want to use it in a public facing area without lots of testing.
Also check out the Railscast on it: http://railscasts.com/episodes/176-searchlogic
1.You can do some coding in your controller post as such:-
<pre>
def show
#post = Post.find(params[:id])
#tag_counts = Tag.count(:group => :name, :order => 'updated_at DESC', :limit => 10)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
</pre>
2.Now make some changes in your view file:-
<pre>
<b>Tags:</b>
<%= join_tags(#post) %>
<%unless #tag_counts.nil?%>
<% #tag_counts.each do |tag_name, tag_count| %>
<tr><td><%= link_to(tag_name, posts_path(:name => tag_name)) %></td>
<td>(<%=tag_count%>)</td>
</tr><% end %>
<%end%>
</pre>
3. And one important thing is that there should be many to many relationship between tags and post.

Resources