Alias method chain in ruby calling itself - ruby-on-rails

I am rewriting the controller render method, however, I want to use the old method when in the render_to_string method. These are my current codes:
def render_with_xhr(options = {}, extra_options = {}, xhr_check = true, &block)
if xhr_check && request.xhr?
template = render_to_string(options)
render_without_xhr(:update) {|page| page.replace_html("#popup .dialog", template)}
else
render_without_xhr(options, extra_options, &block)
end
end
alias_method_chain :render, :xhr
What happens is that since render_to_string makes use of render (presumably), I end up in an infinite loop. How can I make it fall back to the old method just for that line my new render method?
I tweaked the codes from the accepted answer, final code is below:
def render_to_string(options = {}, &block)
render(options, {}, false, &block)
ensure
response.content_type = nil
erase_render_results
reset_variables_added_to_assigns
end
def render_with_xhr(options = nil, extra_options = {}, xhr_check = true, &block)
if xhr_check && request.xhr?
template = render_to_string(options)
render_without_xhr :update do |page|
page.replace_html("#popup .dialog", template)
end
else
render_without_xhr(options, extra_options, &block)
end
end
alias_method_chain :render, :xhr

you could at line 2 pass some unique value to options hash, and then detect it in your code and remove
def render_with_xhr(options = {}, extra_options = {}, xhr_check = true, &block)
if xhr_check && request.xhr? && !options.delete(:bacon)
template = render_to_string(options.merge(:bacon => true))
render_without_xhr(:update) {|page| page.replace_html("#popup .dialog", template)}
else
render_without_xhr(options, extra_options, &block)
end
end
alias_method_chain :render, :xhr
like that :)

Related

Rails: refactor code with blocks

I use Rails 4.2 and i want to refactor my helper method to get rid of duplicate code:
In app/helpers/admin/tasks_helper.rb
def chosen_select(name, method, chzes, selected = nil, options = {}, html_options = {})
options[:value_method] ||= :id
options[:text_method] ||= :name
if options.key?(:placeholder)
html_options['data-placeholder'.intern] = options[:placeholder]
options.delete(:placeholder)
end
if html_options.key?(:class)
html_options[:class] = 'chosen-select ' + html_options[:class]
else
html_options[:class] = 'chosen-select'
end
chzes = options_from_collection_for_select(chzes, options[:value_method], options[:text_method], selected)
options.delete(:value_method)
options.delete(:text_method)
select(name, method, chzes, options.merge!(include_hidden: false), html_options)
end
def chosen_select_array(name, method, chzes, selected = nil, options = {}, html_options = {})
options[:value_method] ||= :id
options[:text_method] ||= :name
if options.key?(:placeholder)
html_options['data-placeholder'.intern] = options[:placeholder]
options.delete(:placeholder)
end
if html_options.key?(:class)
html_options[:class] = 'chosen-select ' + html_options[:class]
else
html_options[:class] = 'chosen-select'
end
chzes = options_for_select(chzes, selected)
options.delete(:value_method)
options.delete(:text_method)
select(name, method, chzes, options.merge!(include_hidden: false), html_options)
end
I my view i have a lot of method calls like app/views/admin/tasks/index.html.erb
<%= chosen_select(:select, :project_id, [TaskFilterOptgroups.active_projects, TaskFilterOptgroups.inactive_projects] , #task_filter_configuration.project_id, {:include_blank => true, :placeholder => 'Project'}, {'data-last-project_id' => #task_filter_configuration.project_id, :style => 'width: 150px;'}) %>
so that i don't want to change my method calls in the view.
My attempt is to make a generic method "chosen_select_generic" that will be called from the specific method like "chosen_select":
def chosen_select_generic(name, method, chzes, selected = nil, options = {}, html_options = {})
options[:value_method] ||= :id
options[:text_method] ||= :name
if options.key?(:placeholder)
html_options['data-placeholder'.intern] = options[:placeholder]
options.delete(:placeholder)
end
if html_options.key?(:class)
html_options[:class] = 'chosen-select ' + html_options[:class]
else
html_options[:class] = 'chosen-select'
end
# 2 different chzes in 2 methods:
# 1) chosen_select(...)
# chzes = options_from_collection_for_select(chzes, options[:value_method], options[:text_method], selected)
# 2) chosen_select_array(...)
# chzes = options_for_select(chzes, selected)
yield chzes
options.delete(:value_method)
options.delete(:text_method)
select(name, method, chzes, options.merge!(include_hidden: false), html_options)
end
and then chosen_select could look like:
def chosen_select(name, method, chzes, selected = nil, options = {}, html_options = {})
chosen_select_generic(name, method, chzes, selected = nil, options = {}, html_options = {}) do |contents|
chzes = option_groups_from_collection_for_select(chzes, :entries, :status, options[:value_method], options[:text_method], selected)
end
end
But this doesn't work. How can i extract the duplicate code in a block without changing the method calls in the view?
This assignment in your block won't do what you think:
chzes = option_groups_from_collection_for_select(...)
It creates a new local variable instead of changing external one. If it's the only changeable piece here, then you can just return it from the block:
chosen_select_generic(name, method, chzes, selected = nil, options = {}, html_options = {}) do |chzes|
option_groups_from_collection_for_select(chzes, :entries, :status, options[:value_method], options[:text_method], selected)
end
And receive the value in your generic method like this:
chzes = yield(chzes)

Ruby on Rails filter array using three fields

I am trying to search through my model using 3 columns. Also if the column is empty, it is valid. This is how I am doing it
def getactivityfortoday
#temp = params[:temp]
logger.debug "params temp:#{#temp.inspect}"
#sky = params[:sky]
#day = params[:day]
#todaysactivities = []
#activities=[]
#finaldata = []
#activities = Weatherclockactivity.all
#attemptactivities = []
#attemptactivities = #user.attempts
for activity in #activities do
logger.debug "activity: #{activity.attributes.inspect}"
if #temp.to_i < activity.temperatureMax.to_i && #temp.to_i > activity.temperatuureMin.to_i
if #sky == activity.sky || activity.sky == ""
if #day == activity.day
#todaysactivities << activity
end
end
end
end
for activity in #todaysactivities
for attempt in #attemptactivities
if attempt == activity
finaldata << {activity: activity, attempt: "yes"}
else
finaldata << {activity: activity, attempt: "no"}
end
end
end
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #finaldata }
end
The response I get is an empty array but I should be getting 3 rows as a response.
spelling mistake here
activity.temperatuureMin.to_i
And
finaldata << {activity: activity, attempt: "yes"}
should be
#finaldata << {activity: activity, attempt: "yes"}
Also you could be more concise
def getactivityfortoday
#temp = params[:temp]
logger.debug "params temp:#{#temp.inspect}"
#sky = params[:sky]
#day = params[:day]
#activities = Weatherclockactivity.all
#attemptactivities = #user.attempts
#finaldata = #activities.map do |activity|
if (activity.temperatureMin.to_i + 1...activity.temperatureMax.to_i).include?(#temp.to_i) && ( #sky == activity.sky || activity.sky == "") && #day
#attemptactivities.include?(activity) ? {activity: activity, attempt: "yes"} : {activity: activity, attempt: "no"}
end
end.compact
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #finaldata }
end
end
How about something like this?
I tried to make it a balance of readability and conciseness. First we filter for the desired activities. Then we structure the output. This should be easier to debug.
def getactivityfortoday
#temp = params[:temp].to_i
#sky = params[:sky]
#day = params[:day]
#activities = Weatherclockactivity.all
#attemptactivities = #user.attempts
selected_activities = #activities.select do |activity|
# Make sure it's the right temperaure
return false unless (activity.temperatureMin.to_i + 1 ... activity.temperatureMax.to_i).include? #temp
# Make sure the sky matches, or the sky is blank
return false unless (#sky.blank? || #sky.activity == activity.sky)
# Make sure the day matches
return false unless #day == activity.day
# Otherwise, it's good!
return true
end
selected_attempted_activities = selected_activities.map do|activity|
ret = {activity: activity}
ret[:attempt] = #attemptactivities.include?(activity) ? "yes" : "no"
ret
end
respond_to do |format|
format.html { render action: "new" }
format.json { render json: selected_attempted_activities }
end
end
There are a few typos in your original (for instance, #finaldata not finaldata). Make sure that you spell instance variables (things starting with #, like #sky) correctly, since if you try to access an undefined instance variable, it'll silently default to nil.
The best and flexible way is to use ActiveModel::Model
It allows you to use many more useful methods.
it will seems like:
app/models/activity_report.rb
Class ActivityReport
include ActiveModel::Model
attr_accessor :day, :activity # and etc.
validates :day, presence: true
def day
#day.to_s # for example
end
def day=(value)
#day = value - 1.month # for example every date which user set will set on one month ago
end
# and etc
end
app/controllers/posts_controller.rb
...
def index
#activity = ActivityReport.new(params[:activity])
end
def create
#activity.create!
end
...
app/views/posts/index.html.haml
= form_for #activity do |f|
= f.day
For more information you could take a look at:
http://edgeapi.rubyonrails.org/classes/ActiveModel/Model.html
http://railscasts.com/episodes/219-active-model (old)
http://railscasts.com/episodes/416-form-objects (newer, but a little complex)

How Do I Pass A Block Through To A Method Call?

I need a helper that generates a link wrapped in a <li> including an active class.
Without supporting blocks this is easy:
def nav_item(*args, &block)
url = args[1]
clazz = 'active' if current_page?(url)
content_tag(:li, :class => clazz) do
link_to(*args)
end
end
But like link_to I want my helper to support blocks for defining content. With link_to I can do:
So how do I support the same in my helper?
All I need to do is pass the block through to link_to. My current attempt
def nav_item(*args, &block)
url = if block_given?
args.first
else
args[1]
end
clazz = 'active' if current_page?(url)
content_tag(:li, :class => clazz) do
if block_given?
# What goes here?
else
link_to(*args)
end
end
end
You can just pass the block to link_to as the last arg. Like this:
def nav_item(*args, &block)
url = if block_given?
args.first
else
args[1]
end
clazz = 'active' if current_page?(url)
content_tag(:li, :class => clazz) do
if block
link_to(*args, &block)
else
link_to(*args)
end
end
end

Rails routing - how to add scope param to url_for helper?

I have resource bio and in views and link for add new bio is:
= link_to "Add new bio", [:new, :admin, :bio]
If I put resource :bio to scope like this:
namespace :admin do
scope "/:bio_type", :defaults => {:bio_type => "company"} do
resources :bios
end
end
This doesn't work
= link_to "Add new bio", [:new, :admin, :bio, { bio_type: params[:bio_type] }]
My question is how can I add scoped param to url_for helper? And can rails do this by default?
p.s. new_admin_bio_path({bio_type: params[:bio_type]}) works fine, but it's just curious
I believe you cannot make this with array params to link_to. You have to use polymorphic_path or new_admin_bio_path({bio_type: params[:bio_type]})
The reason is that link_to calls url_for with [:new, :admin, :bio, { bio_type: params[:bio_type] }], which calls polymorphic_path with these params.
Check the source code for url_for and for polymorphic_url.
Notice, that polymorphic_url takes 2 params - record_or_hash_or_array and options, but url_for calls it with one parameter only.
def url_for(options = {})
options ||= {}
case options
when String
options
when Hash
options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
super
when :back
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
polymorphic_path(options)
end
end
def polymorphic_path(record_or_hash_or_array, options = {})
polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
end
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
proxy = record_or_hash_or_array.shift
end
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
record = extract_record(record_or_hash_or_array)
record = convert_to_model(record)
args = Array === record_or_hash_or_array ?
record_or_hash_or_array.dup :
[ record_or_hash_or_array ]
inflection = if options[:action] && options[:action].to_s == "new"
args.pop
:singular
elsif (record.respond_to?(:persisted?) && !record.persisted?)
args.pop
:plural
elsif record.is_a?(Class)
args.pop
:plural
else
:singular
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
url_options = options.except(:action, :routing_type)
unless url_options.empty?
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
args.collect! { |a| convert_to_model(a) }
(proxy || self).send(named_route, *args)
end
So, correct call with the scope option should sound like
polymorphic_path([:new, :admin, :bio], bio_type: params[:bio_type])

How do I make link_to open external URLs in a new window?

I need to convert a rails 2.3 site so that all external URLs open in a new window. I could go though every call to link_to and add :target => '_blank', but I'd like to do it in one step for all links, present and future. Is there a way I can monkey patch link_to to get the desired behaviour?
You should not have to change your server-side code for this view problem.
You should use Unobscursive javascript.
This example will only make external links showing up in a new window :
// jQuery
//
$(document).ready(function() {
$("a").click(function() {
link_host = this.href.split("/")[2];
document_host = document.location.href.split("/")[2];
if (link_host != document_host) {
window.open(this.href);
return false;
}
});
});
In the end I went with this, in an initialiser:
module ExternalLinksInNewTabs
def new_tab_link_to *args, &block
if block_given?
options = args.first || {}
html_options = args[1] || {}
if options.is_a? String
if ExternalLinksInNewTabs.is_external_link? #controller.request.host, options
html_options[:target] = '_BLANK'
end
end
same_tab_link_to options, html_options, &block
else
name = args.first
options = args[1] || {}
html_options = args[2] || {}
if options.is_a? String
if ExternalLinksInNewTabs.is_external_link? #controller.request.host, options
html_options[:target] = '_BLANK'
end
end
same_tab_link_to name, options, html_options
end
end
def self.is_external_link? host, url
host.sub! /^www\./, ''
url =~ /^http/i && url !~ /^http:\/\/(www\.)?#{host}/i
end
end
module ActionView
module Helpers
module UrlHelper
include ExternalLinksInNewTabs
alias_method :same_tab_link_to, :link_to
alias_method :link_to, :new_tab_link_to
end
end
end
You just add an helper to add this options in your link_to
If you want add it on each link_to to can add on ApplicationHelper
def link_to(*args, &block)
if block_given?
args = [(args.first || {}), (args.second || {}).merge(:target => '_blank')]
else
args = [(args.first || {}), (args.second || {}), (args.third || {}).merge(:target => '_blank')]
end
super(args, block)
end
Or you can create your own link_to helper
def link_to_blank(*args, &block)
if block_given?
args = [(args.first || {}), (args.second || {}).merge(:target => '_blank')]
else
args = [(args.first || {}), (args.second || {}), (args.third || {}).merge(:target => '_blank')]
end
link_to(args, block)
end
In rails 3.2+, it has been added as an option, just add
= link_to 'facebook', 'http://www.facebook.com/fb-page', target: '_blank'
and it'll open the link in a new tab.

Resources