How to create case(switch) statement with a hash of constants using ruby on rails? - ruby-on-rails

I want to create the equivalent of this code, except using a hash and some meta programming.
def current_verb
case params[:controller]
when "apps"
#current_verb = "MADE "
when "articles"
#current_verb = "LEARNED "
when "articles"
#current_verb = "WONDERED " # Change later to specify articles with a certain tag.
else
#current_verb = "IS "
end
end
Intuitively, I tried something like this, but it seems to be invalid.
So far I have this in the application_controller.rb
class ApplicationController < ActionController::Base
before_action :current_verb
private
verbs = { pages: "IS",
apps: "MADE",
articles: "TAUGHT",
articles: "WONDERED"
}
def current_verb
case params[:controller]
verbs.each |key, value| do
# need this to spit out literal code, not evaluate code
when key
#current_verb = value
end
else
#current_verb = verbs[:pages]
end
end
The tricky part about this is that I don't think I can use define_method or send because the looped portion isn't the entirety of the method. Thanks for the help.

It looks like you're just looking up a key in a hash and returning its value. The only caveat appears to be that if the ke isn't found then you want to return a default value of "IS". I would suggest using Hash#fetch for this.
class ApplicationController < ActionController::Base
VERBS = { pages: "IS",
apps: "MADE",
articles: "TAUGHT",
articles: "WONDERED"
}.stringify_keys.freeze
before_action :set_current_verb
private
def set_current_verb
#current_verb = VERBS.fetch(controller_name) { "IS" }
end
end
Note that I made VERBS a constant so that it would be visible to the set_current_verb method. And I included it above the private designation because constants can't be private anyway. Using the VERBS hash inside of the set_current_verb method would cause it to be evaluated every single time, so the constant is a better solution still. Also, controller_name is preferred over params[:controller] for its expressiveness and because it will avoid returning namespaces should any exist in the future.
Also, the VERBS hash seems to have two identical keys. I assume that's just a typo and can be corrected by you.

Related

Rails how to use where method for search or return all?

I am trying to do a search with multiple attributes for Address at my Rails API.
I want to search by state, city and/or street. But user doesn't need to send all attributes, he can search only by city if he wants.
So I need something like this: if the condition exists search by condition or return all results of this condition.
Example:
search request: street = 'some street', city = '', state = ''
How can I use rails where method to return all if some condition is nil?
I was trying something like this, but I know that ||:all doesn't work, it's just to illustrate what I have in mind.:
def get_address
address = Adress.where(
state: params[:state] || :all,
city: params[:city] || :all,
street: params[:street] || :all)
end
It's possible to do something like that? Or maybe there is a better way to do it?
This is a more elegant solution using some simple hash manipulation:
def filter_addesses(scope = Adress.all)
# slice takes only the keys we want
# compact removes nil values
filters = params.permit(:state, :city, :street).to_h.compact
scope = scope.where(filters) if filters.any?
scope
end
Once you're passing a column to where, there isn't an option that means "on second thought don't filter by this". Instead, you can construct the relation progressively:
def get_address
addresses = Address.all
addresses = addresses.where(state: params[:state]) if params[:state]
addresses = addresses.where(city: params[:city]) if params[:city]
addresses = addresses.where(street: params[:street]) if params[:street]
addresses
end
I highly recommend using the Searchlight gem. It solves precisely the problem you're describing. Instead of cluttering up your controllers, pass your search params to a Searchlight class. This will DRY up your code and keep your controllers skinny too. You'll not only solve your problem, but you'll have more maintainable code too. Win-win!
So in your case, you'd make an AddressSearch class:
class AddressSearch < Searchlight::Search
# This is the starting point for any chaining we do, and it's what
# will be returned if no search options are passed.
# In this case, it's an ActiveRecord model.
def base_query
Address.all # or `.scoped` for ActiveRecord 3
end
# A search method.
def search_state
query.where(state: options[:state])
end
# Another search method.
def search_city
query.where(city: options[:city])
end
# Another search method.
def search_street
query.where(street: options[:street])
end
end
Then in your controller you just need to search by passing in your search params into the class above:
AddressSearch.new(params).results
One nice thing about this gem is that any extraneous parameters will be scrubbed automatically by Searchlight. Only the State, City, and Street params will be used.

Interpolating an attribute's key before save

I'm using Rails 4 and have an Article model that has answer, side_effects, and benefits as attributes.
I am trying to create a before_save method that automatically looks at the side effects and benefits and creates links corresponding to another article on the site.
Instead of writing two virtually identical methods, one for side effects and one for benefits, I would like to use the same method and check to assure the attribute does not equal answer.
So far I have something like this:
before_save :link_to_article
private
def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.key.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.key = linked_attrs.join('; ')
end
end
end
but chaining on the key like that gives me an undefined method 'key'.
How can I go about interpolating in the attribute?
in this bit: self.key you are asking for it to literally call a method called key, but what you want, is to call the method-name that is stored in the variable key.
you can use: self.send(key) instead, but it can be a little dangerous.
If somebody hacks up a new form on their browser to send you the attribute called delete! you don't want it accidentally called using send, so it might be better to use read_attribute and write_attribute.
Example below:
def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.read_attribute(key).split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.write_attribute(key, linked_attrs.join('; '))
end
end
end
I'd also recommend using strong attributes in the controller to make sure you're only permitting the allowed set of attributes.
OLD (before I knew this was to be used on all attributes)
That said... why do you go through every single attribute and only do something if the attribute is called answer? why not just not bother with going through the attributes and look directly at answer?
eg:
def link_to_article
linked_attrs = []
self.answer.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.answer = linked_attrs.join('; ')
end

rails get output from controller methods?

I have the following code in my Application Controller
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def get_mkts(all_idx)
mkts = Set.new
all_idx.each do |idx|
m = decode_index_names(idx)
puts m[:mkt]
mkts.add(m[:mkt])
end
end
def decode_index_names(name)
mkt = name.split(/[0-9]/)[0]
type = get_first_num(mkt);
{:mkt => mkt,:type => type}
end
def get_first_num(str)
str[/\d+/]
end
end
And I'm inputting an array of strings like this:
["USEQUITIES2tv10", "USEQUITIES2tv15", "USEQUITIES2tv20", "NONUSEQUITIES2tv5", "NONUSEQUITIES2tv10", "NONUSEQUITIES2tv15", "NONUSEQUITIES2tv20", "BONDS2tv5", "BONDS2tv10", "BONDS2tv15", "BONDS2tv20"
, "ES1", "ES2tv5", "ES2tv10", "ES2tv15", "ES2tv20", "NQ1", "NQ2tv5", "NQ2tv10", "NQ2tv15", "USBONDS2tv5", "USBONDS2tv10", "USBONDS2tv15", "USBONDS2tv20", "GERMANBONDS2tv5", "GERMANBONDS2tv10", "GERMANB
ONDS2tv15", "GERMANBONDS2tv20", "EQUITIESnBONDS2tv5", "EQUITIESnBONDS2tv10", "EQUITIESnBONDS2tv15", "EQUITIESnBONDS2tv20", "COMMODITIES2tv5", "COMMODITIES2tv10", "COMMODITIES2tv15", "COMMODITIES2tv20",
"CURRENCIES2tv5"]
The method get_mkts is supposed to loop through, extract the text up tot the first number and create a unique array of symbols (which is why i used Set). However, I can't get the method to output anything other than the original input. In rails console I'm able to see from the output of "puts m[:mkt]" that each loop through is getting the correct value, I just don't know how to return the set mkts instead of the input value. Any ideas?
Ruby methods return the result of the last statement if you don't use return. In your case it's each and that's why you get the input back. You can do something like this:
def get_mkts(all_idx)
mkts = Set.new
all_idx.each do |idx|
m = decode_index_names(idx)
puts m[:mkt]
mkts.add(m[:mkt])
end
mkts
end
This will return the mkts set instead of all_idx.
The method can be rewritten has:
def get_mkts(all_idx)
all_idx.map { |idx| decode_index_names(idx) }.uniq
end
Looks more rubyish and its shorter and cleaner

Ruby on Rails: Execute Logic Based on Selected Menu

I have a class that I use to contain select menu options for property types. It works fine. However, I need to be able to verify the selection and perform specific logic based on the selected option. This needs to happen in my Ruby code and in JavaScript.
Here is the class in question:
class PropertyTypes
def self.[](id)
##types[id]
end
def self.options_for_select
##for_select
end
private
##types = {
1 => "Residential",
2 => "Commercial",
3 => "Land",
4 => "Multi-Family",
5 => "Retail",
6 => "Shopping Center",
7 => "Industrial",
8 => "Self Storage",
9 => "Office",
10 => "Hospitality"
}
##for_select = ##types.each_pair.map{|id, display_name| [display_name, id]}
end
What is the best way to verify the selection? I need to perform specific logic and display user interface elements based on each type of property type.
Since I am storing the id, I would be verifying that the id is a particular property type. Something like:
PropertyTypes.isResidential?(id)
Then this method would look like this:
def self.isResidential?(id)
##types[id] == "Residential"
end
But now I am duplicating the string "Residential".
For JavaScript, I assume I would make an ajax call back to the model to keep the verification code DRY, but this seems like over kill.
Do I need to manually create a verification method for each property type or can I use define_method?
This seems so basic yet I am confused and burned out on this problem.
Thanks
===
Here's my solution:
class << self
##types.values.each do |v|
# need to remove any spaces or hashes from the found property type
v = v.downcase().gsub(/\W+/, '')
define_method "is_#{v}?", do |i|
type_name = ##types[i]
return false if type_name == nil #in case a bogus index is passed in
type_name = type_name.downcase().gsub(/\W+/, '')
type_name == v
end
end
end
It sounds like you can benefit from some Ruby meta-programming. Try googling "ruby method_missing". You can probably do something quick & dirty along the lines of:
class PropertyTypes
def method_missing(meth, *args, &block)
if meth.to_s =~ /^is_(.+)\?$/
##types[args.first] == $1
else
super
end
end
end
On the ruby side you could also use something like this to define dynamically these methods:
class << self
##types.values.each do |v|
define_method "is_#{v}?", do |i|
##types[i] == v
end
end
end

URL encoded route to Rails controller

I have a URL encoded resource such as:
http://myurl/users/Joe%20Bloggs/index.xml
This is for a RESTful webservice which uses user logins in the path. The problem is that the controller in rails doesn't seem to decode the %20. I get the following error:
ActionController::RoutingError (No route matches "/Joe%20Bloggs/index.xml" with {:method=>:post}):
What I'm actually trying to do is achieve one of 2 options (using authlogic as my registrations handler):
Either (preferably) allow users to register user names with spaces in them, and have these get routed correctly to my controller. Authlogic by default allows spaces & #/. characters - which is just fine with me if I can make it work...
Or I can restrict authlogic to dissallow the spaces. I know I can do this with:
.merge_validates_format_of_login_field_options...
but I'm not entirely sure of the correct syntax to provide the new regex and return message on failure...
Any suggestions greatly appreciated!
Generally it's a better idea to have a URL-safe "slug" field in your models for situations like this. For example:
class User < ActiveRecord::Base
before_validation :assign_slug
def to_param
# Can't use alias_method on methods not already defined,
# ActiveRecord creates accessors after DB is connected.
self.slug
end
def unique_slug?
return false if (self.slug.blank?)
if (new_record?)
return self.class.count(:conditions => [ 'slug=?', self.slug ]) == 0
else
return self.class.count(:conditions => [ 'slug=? AND id!=?', self.slug, self.id ]) == 0
end
end
def assign_slug
return if (slug.present?)
base_slug = self.name.gsub(/\s+/, '-').gsub(/[^\w\-]/, '')
self.slug = base_slug
count = 1
# Hunt for a unique slug starting with slug1 .. slugNNN
while (!unique_slug?)
self.slug = base_slug + count.to_s
count += 1
end
end
end
This may solve the problem of having non-URL-friendly names. Rails is particularly ornery when it comes to having dots in the output of to_param.

Resources