ruby on rails implement a search form that searches multiple relations - ruby-on-rails

I've implemented a simple search form (according to the "simple form" screencast) that searches the "illnesses" table in my DB.
Now I want the same search box to search both the "illnesses" table and the "symptoms" table.
My code currently looks like this:
main_page\index.html.erb:
<b>Illnesses</b>
<%= form_tag illnesses_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %><br/>
<%= submit_tag "Illnesses", :name => nil %><br/>
</p>
illnesses_controller.rb:
class IllnessesController < ApplicationController
def index
#illnesses = Illness.search(params[:search])
respond_to do |format|
format.html # index.html.erb
format.json { render json: #illnesses }
end
...
end
illness.rb:
class Illness < ActiveRecord::Base
...
def self.search(search)
if search
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all)
end
end
Could you please guide me how to implement this extension?
I'm a beginner (obviously) and I don't really know what should be the "form_tag" action, where should I implement it and which class should implement the extended search...
Thanks,
Li

Hmm, to just easily set off presuming you have a Symptom class similar to the Illness class(btw it would be most clean if you refactored the search functionality into a module and then include this module in both classes) then you can do:
class IllnessesController < ApplicationController
def index
#results = Illness.search(params[:search]) + Symptom.search(params[:search])
...
end
end
But maybe you would like to refactor the name of the controller, because now it is not anymore Illness specific. Also notice that we are here using two search queries instead of one so it is not optimal, but saves you the pain of sending a pure SQL query for two types of models at the same time.
Okay for the module. If you are not familiar with modules they might seem a little weird but they are little more than a piece of code that can be shared across classes to keep things DRY which is our case too. You can imagine including the module as taking the code from the module and evaluating it (courtesy of interpreted languages) in the context of the class which has the same result as if the module code was hard coded into the class itself. So the module looks like:
module Search
def self.search(token)
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
end
end
And now any class, if it implements the find method(ActiveRecord API) can happily include this module and enjoy the search functionality like this:
require 'path/to/search'
class Foo < ActiveRecord::Base
include Search
end
That's it. Now if you need to tweak your search code, you change it in one place and it propagates to all the includers. You can also create modules inside modules which is sometimes used like this:
require 'active_support/concern'
module Search
extend ActiveSupport::Concern
included do
#Stuff that gets done when the module is included (new validations, callbacks etc.)
end
module ClassMethods
#Here you define stuff without the self. prefix
end
module InstanceMethods
#Instance methods here
end
end
But some of it is convention that is defined in ActiveSupport::Concern so not everything will probably work in pure ruby. I encourage you to experiment with these things, they make ruby real fun. Oh, extend is very much like include, only, as I understand it, it kind of evaluates on the class level. So all instance methods of the included module would become class methods of the includer module if you follow me. Have fun!

Related

Reusing code ruby on rails

I've got a module in my project in lib/. it's content is like this :
module Search
module Score
def get_score
return 'something'
end
end
end
This Search has many different modules I need to use Score. I realize I need to add require in my model (I'm trying to use this from model). So here is my code (model) :
require 'search'
class User < ActiveRecord::Base
def get_user_score
#tried this :
p Search::Score.get_score #error
#this as well
score_instance = Score.new #error
score = Search::Score.get_score # error undefined method `get_score'
end
end
So how do I reuse the code I have in other class (module)?
To get it working you can either mix the module into your class:
require 'search'
class User < ActiveRecord::Base
include Search::Score
def get_user_score
p get_score # => "something"
end
end
Or you can define the method inside your module similar to class methods:
module Search
module Score
def self.get_score
return 'something'
end
end
end
If you do that, you can call get_score like expected:
require 'search'
class User < ActiveRecord::Base
def get_user_score
p Search::Score.get_score # => "something"
end
end
See this tutorial for a more in depth explanation about modules in Ruby.
First, see "Best Practices for reusing code between controllers in Ruby on Rails".
About reuse code as a module, take a look at "Rethinking code reuse with Modularity for Ruby".
"Modules are crippled classes"
Modules are like crippled classes in Ruby. If you look into the inheritance chain you see that a Class actually inherits from Module.
Module cannot be instanciated. So the call to .new is not working.
What you CAN do however is to specify your method as a 'class' method (I know I said it is not a class...)
So you would add a self in front like this:
module Search
module Score
def self.get_score
return 'something'
end
end
end
Then you can call this method as a class method like you tried in your code example
Search::Score is a module and not a class, so Score.new will not work.
You can try to change the signature of the get_score function to self.get_score.
In addition to def self.get_score in the above answers, there is also extend self, like so:
module Search
module Score
extend self
def get_score
return 'something'
end
end
end
and module_function:
module Search
module Score
module_function
def get_score
return 'something'
end
end
end
The latter is actually the preferred method in RuboCop (source), though in practice I personally have not seen it so often.

Add Facebook metadata to Rails pages with sub-pages

I've tried Facebook's Open Graph protocol in adding meta data on Rails pages. What I want to do now is to make my code not duplicated or D.R.Y.---instead of putting one meta-data header for each controller page I have, I'd like to create a base class called "MyMetaBuilder" which will be inherited by the sub-pages, but don't know where and how to start coding it...
Someone suggested that meta data property values must be dynamically generated depending on the context. For example, PlayMetaBuilder, CookMetaBuilder and so on...
Also, when unit testing the controller action, how do I verify for its existence?
Thanks a lot.
One thing is defining the tags, another is rendering them. I would do the following:
write a controller mixin (something like acts_as_metatagable) where I would define specific fields for each controller (and populate the remaining with defaults). These would be assigned to a class (or instance) variable and in this way be made accessible in the rendering step).
write an helper function which would take all my tags and turn them into html. This helper function would then be called in the layout and be rendered in the head of the document.
so, it would look a bit like this:
# homepage_controller.rb
class HomepageController < ActionController::Base
# option 1.2: include it directly here with the line below
# include ActsAsMetatagable
acts_as_metatagable :title => "Title", :url => homepage_url
end
# lib/acts_as_metatagable.rb
module ActsAsMetatagable
module MetatagableMethods
#option 2.2: insert og_tags method here and declare it as helper method
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
def self.included(base)
base.helper_method :og_tags
end
end
def acts_as_metagabable(*args)
include MetatagableMethods
# insert dirty work here
end
end
# option 1.1: include it in an initializer
# initializers/acts_as_metatagable.rb
ActiveController::Base.send :include, ActsAsMetatagable
# option 2.1: insert og_metatags helper method in an helper
module ApplicationHelper
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
end
What I did for Scoutzie, was put all metadata into a head partial, with if/else cases as such:
%meta{:type => 'Author', :content => "Kirill Zubovsky"}
%meta{'property' => "og:site_name", :content=>"Scoutzie"}
-if #designer
...
-elsif #design
...
-else
...
This way, depending on the variables that load, I know which page it is, and thereby know which metadata to include. This might not be an elegant solution, but it works and it's really simple.

Ruby metaprogramming inside model

I have a model called Snippet, which contains snippets of HTML to push into views.
The model has a column CODE and another CONTENT
I'd like to write something like this in my view and get the content back
<%= raw Snippet.PHONE_NUMBER %>
which looks up PHONE_NUMBER on CODE and returns CONTENT
Add a method_missing class method in Snippet class as follows
# Snippet class
class << self
def method_missing(method, *args, &block)
if(snippet = Snippet.find_by_code(method.to_s))
return snippet.content
else
return super(method, *args, &block)
end
end
end
This should do the trick.
However, on a related note, I'm not sure if doing this would be the best way to go because your code is dependent on the data in your database. Tomorrow, the record for phone number gets removed and your code Snippet.PHONE_NUMBER would break. There is a lot maintenance headache in this approach.
A cleaner approach (which would avoid metaprogramming) would have your view do something like this:
<%= snippet :PHONE_NUMBER %>
or
<%= snippet 'PHONE_NUMBER' %>
where the snippet method is defined in a helper module like this:
module SnippetHelper
def snippet(code)
raw Snippet.find_by_code(code.to_s).content
end
end
and made available to all your views with something like this:
class ApplicationController < ApplicationController::Base
helper :snippet
end
Or use delegate.
But it sounds like you're providing another implementation of partials, or helpers, or a combination of decent_exposure and some combination of helpers and partials.

ruby on rails function in helper?

I'm trying to make a function that takes 2 arguments from the user_controller and does a few queries and calculations and return the result in an array. Is this something I should set up in a helper file? And how would I return the results (I'm very new to this).
Thanks for all and any help :)
Unless those calculations are entirely related to your templates (html, js) you should NEVER put that stuff in helpers of any kind. It will make your app hard to test.
Your options:
1) Create a model without ActiveRecord (MyCalculations.rb in ˜/models or ~/lib)
2) Create an extension and include in existing models
Examples (sample code, not realistic)
calculator.rb in ~/models
class Calculator
attr_accessor :amount, :parcs, :interest, :change
#... lots of code
def initialize(amount, parcs, interest)
# do stuff
calculate
end
def self.calculate!(amount, parcs, interest)
Calculator.new(amount, parcs, interest)
end
end
Extensions:
~/lib/models/import/csv_ext.rb
module Models
module Import
module CsvExt
extend ActiveSupport::Concern
included do
end
module ClassMethods
#static
def load_from_csv(csv)
# code comes here
end
end
end
end
end
Then, add it to your models:
include Models::Import::CsvExt

searchlogic with globalize2?

Given there is a model:
class MenuItem < ActiveRecord::Base
translates :title
end
and searchlogic is plugged in, I'd expect the following to work:
>> MenuItem.search(:title_like => 'tea')
Sadly, it doesn't:
Searchlogic::Search::UnknownConditionError: The title_like is not a valid condition. You may only use conditions that map to a named scope
Is there a way to make work?
P.S.
The closest I managed to get workging, was:
>> MenuItem.search(:globalize_translations_title_like => 'tea')
Which doesn't look nice.
I developed searchlogic. By default, it leverages existing named scopes and the database columns. It can't really go beyond that because ultimately it has to create the resulting SQL using valid column names. That said, there really is no way for searchlogic to cleanly understand what your :title attribute means. Even if it did, it would be specific to the logic defined in your translation library. Which is a red flag that this shouldn't be in the library itself, but instead a plugin or code that gets initialized within your app.
Why not override the method_missing method and do the mapping yourself? Searchlogic provides and easy way to alias scoped by doing alias_scope:
alias_scope :title_like, lambda { |value| globalize_translations_title_like(value) }
Here's a quick stab (this is untested):
module TranslationsMapping
def self.included(klass)
klass.class_eval do
extend ClassMethods
end
end
module ClassMethods
protected
def method_missing(name, *args, &block)
translation_attributes = ["title"].join("|")
conditions = (Searchlogic::NamedScopes::Conditions::PRIMARY_CONDITIONS +
Searchlogic::NamedScopes::Conditions::ALIAS_CONDITIONS).join("|"))
if name.to_s =~ /^(#{translation_attributes})_(#{conditions})$/
attribute_name = $1
condition_name = $2
alias_scope "#{attribute_name}_#{condition_name}", lambda { |value| send("globalize_translations_#{attribute_name}_#{condition_name}", value) }
send(name, *args, &block)
else
super
end
end
end
end
ActiveRecord::Base.send(:include, TranslationsMapping)
Hope that helps. Again, I haven't tested the code, but you should get the general idea. But I agree, the implementation of the translations should be behind the scenes, you really should never be typing "globalize_translations" anywhere in your app, that should be take care of transparently on the model level.

Resources