Whats this ruby function doing - ruby-on-rails

In this project I poke at, (I am PHP dev, not RoR), there is this function on a modal.
def task
#task ||= if search_key
Project.trop_fish.tasks.find(:first, :conditions => ["description like ?", "Search key: #{search_key}%"])
else
Project.trop_fish.tasks.find(:first, :conditions => ["(name = ? OR name like ?)","#{task_name}","#{task_name} {%}"])
end
end
So it's trying to find a task, from the project called trop_fish.
But whats the #task at the top.
Is it, assign the result of the finds from the if block to the #task?
Is it the same as
def task
if search_key
#task = Project.trop_fish.tasks.find(:first, :conditions => ["description like ?", "Search key: #{search_key}%"])
else
#task = Project.trop_fish.tasks.find(:first, :conditions => ["(name = ? OR name like ?)","#{task_name}","#{task_name} {%}"])
end
end

Almost, not quite. It is the same thing as this:
def task
if search_key
#task ||= Project.trop_fish.tasks.find(:first, :conditions => ["description like ?", "Search key: #{search_key}%"])
else
#task ||= Project.trop_fish.tasks.find(:first, :conditions => ["(name = ? OR name like ?)","#{task_name}","#{task_name} {%}"])
end
end
The ||= indicates that the variable will only be set to the new value if it is not already set with a different value. As some people commenting have pointed out/to put it more simply, #task will be set to the new value if it is nil or false.
This portion of the RoR tutorial by Michael Hartl is a great explanation of the ||= operator.
#pguardino brings up a good point in that a PHP programmer may not be familiar with the fact that if there is no explicit return statement within a method in ruby, it will return the last non-conditional statement in the method as it's return value, so yes, #task is being returned.
There is another bit of text in the RoR tutorial which explains why it is advantageous to use the ||= operator when returning from a method. It is useful because it means the first call to the task method will perform an operation against the database to retrieve a task, but subsequent calls to the method within the same thread will return #task without making calls to the database (since the #task variable has already been set.

Related

Rails 3: Search method returns all models instead of specified

What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand

Setting default search parameter on Ransack for rails

I've been wracking my brain over this but can't get it. I feel like the answer is probably obvious.
What I'm trying to do is the following:
I have an index controller which lists a series of Jobs which I can search using Ransack. Each job has a completion date which either has a date in it or is null (unfinished). Currently, the search itself works great. I would like to make it so that the index page loads up showing only the unfinished work, but I also want it to work so that when someone does run a search, returns results for both finished and unfinished work.
Any help would be greatly appreciated. In the code below, :actual is the name of the field with the completion date. I also was looking around the web and thought that maybe something like the DEFAULT_SEARCH_PARAMETER={} that I have in the Job model might work but I couldn't seem to get it to.
Here is the code:
class Job < ActiveRecord::Base
DEFAULT_SEARCH_PARAMETER ={}
attr_accessible :items_attributes, :actual
end
def index
#search = Job.search(params[:q] || Job::DEFAULT_SEARCH_PARAMETER)
#search.build_condition
#results = #search.result
#job = #results.paginate(:per_page => 10, :page => params[:page])
end
Late to the party, but thought I'd suggest an alternate approach in case someone else comes across this.
The answer above works, but its disadvantage is that the default is not added to Ransack's search object, so - if you are using a search form - the default selection is not shown in the form.
The following approach adds the default to the search object and therefore will appear in your search form.
def index
#search = Job.search(params[:q])
#search.status_cont = 'Open' unless params[:q] #or whatever, must use Ransack's predicates here
#results = #search.result
#job = #results.paginate(:per_page => 10, :page => params[:page])
end
I think you could just apply your own filter when the search parameters don't exist:
def index
#search = Job.search(params[:q])
#results = #search.result
#results = #results.where(:your_date => nil) unless params[:q]
#job = #results.paginate(:per_page => 10, :page => params[:page])
end
Many years later I found myself with this exact problem so I thought I'd chime in with a solution I'm using. Set default search params in the controller and reverse merge them into params[:q]:
def index
default_search_params = {
status_cont: "open"
}
#search = Job.search((params[:q] || {}).reverse_merge(default_search_params))
...
end
So by default, you want the page to load with records where actual is nil. And later when the user searches you want to go back to how your search was working before.
Give this a try.
def index
#search = Job.search(params[:q] || Job::DEFAULT_SEARCH_PARAMETER)
#search.build_condition
#results = #search.result
if #results.nil?
#results=Job.find(:all, :conditions => ["actual = NULL"] )
end
#job = #results.paginate(:per_page => 10, :page => params[:page])
end

Rails 3 displaying tasks from partials

My Tasks belongs to different models but are always assigned to a company and/or a user. I am trying to narrow what gets displayed by grouping them by there due_at date without doing to many queries.
Have a application helper
def current_tasks
if user_signed_in? && !current_company.blank?
#tasks = Task.where("assigned_company = ? OR assigned_to = ?", current_company, current_user)
#current_tasks = #tasks
else
#current_tasks = nil
end
end
Then in my Main view I have
<%= render :partial => "common/tasks_show", :locals => { :tasks => current_tasks }%>
My problem is that in my task class I have what you see below. I have the same as a scope just named due_today. when I try current_tasks.due_today it works if I try current_tasks.select_due_today I get a undefined method "select_due_tomorrow" for #<ActiveRecord::Relation:0x66a7ee8>
def select_due_today
self.to_a.select{|task|task.due_at < Time.now.midnight || !task.due_at.blank?}
end
If you want to call current_tasks.select_due_today then it'll have to be a class method, something like this (translating your Ruby into SQL):
def self.select_due_today
select( 'due_at < ? OR due_at IS NOT NULL', Time.now.midnight )
end
Or, you could have pretty much the same thing as a scope - but put it in a lambda so that Time.now.midnight is called when you call the scope, not when you define it.
[edited to switch IS NULL to IS NOT NULL - this mirrors the Ruby in the question, but makes no sense because it will negate the left of the ORs meaning]

Can I send instance variables to Tequila (.jazz) JSON Parser? (Ruby on Rails)

I am using the great Tequila-JSON Parser ( http://github.com/inem/tequila ) in an Web-application, to render more or less complex JSON server-replies. More and more the JSON-Templates (.jazz) are growing in somehow real "views". I am trying now, to get an instance-variable from the according controller, into the .jazz template, but this somehow fails.
Here is what I am trying to do.
The controller
def get_userlist
#users = User.find(:all, :order => "value DESC", :limit => 10)
#user = User.find_by_email(params[:user_email])
#userid = #user.id # also tried: #userid = 2
respond_to do |format|
format.json
end
end
The .jazz view:
-#users
:only
.nickname
.level
.user_icon_url
.email
:methods
.isfriend(#userid)
+last_checkin
+last_checkin_place
:only
.name
.city
This all returns a pretty valid JSON server-reply, but unfortunately, there is a problem with the
:methods
.isfriend(#userid)
The Method "isfriend" resides in the model "User", is called successfully and returns in the JSON, what it should. But the value of the instance-variable somehow is wrong. Opposed to the above, this one works fine:
:methods
.isfriend(1)
Now the question: Is Tequila not able, to interpret instance-variables in its own .jazz templates? Does anyone have experience, solutions or workarounds?
For the sake of completeness, here is the isfriend method of the User-Model:
def isfriend(user_id)
"Hi, I am User with the id: " + user_id.to_s
end
Nope. Also it doesn't work on Rails 3. I just spent 6 hours trying to port it and got basically nowhere :-(

Intercepting creation of new object

I'm adding a categorization functionality to my app and struggling with it. Objects have many categories through categorizations. I'm trying to intercept the creation of a new categorization, check if theres a similar one, if so, increment it's count, if not, create a new object. Here's what I have so far.
validate :check_unique
protected
def check_unique
categorization = Categorization.where(:category_id => self.category_id, :categorizable_id => self.categorizable_id, :categorizable_type => self.categorizable_type)
if categorization.first
categorization.first.increment(:count)
end
end
This kind of logic should not exist in the controller. This is really business domain and should be in the model. Here's how you should go about it:
categorization = Categorization.find_or_create_by_category_id_and_categorizable_id_and_categorizable_type(self.category_id, self.categorizable_id, self.categorizable_type)
categorization.increment!(:count)
find_or_create will try to find the category in the DB, and if it doesn't exist, it'll create it. Now just make sure that count defaults to zero, and this code will do what you want. (when initially created the count would be 1, then later it'll increment)
PS: I'm not sure if find_or_create has changed in rails 3. But this is the main idea
I decided to move it out of the model object and put it into the controller method creating the categorization. It now works (Yay!) and here's the code if anyone is interested.
def add_tag
object = params[:controller].classify.constantize
#item = object.find(params[:id])
#categories = Category.find(params[:category_ids])
#categories.each do |c|
categorization = #item.categorizations.find(:first, :conditions => "category_id = #{c.id}")
if categorization
categorization.increment!(:count)
else
#item.categorizations.create(:category_id => c.id, :user_id => current_user.id)
end
end
if #item.save
current_user.update_attribute(:points, current_user.points + 15) unless #item.categorizations.exists?(:user_id => current_user.id)
flash[:notice] = "Categories added"
redirect_to #item
else
flash[:notice] = "Error"
redirect_to 'categorize'
end
end

Resources