I'd like to experiment with using form helpers inside the Rails console, but my simple minded approach of doing extend ActionView::Helpers::FormHelper didn't work. For example, subsequently calling form_for resulted in the error NoMethodError: undefined method 'dom_class' for main:Object. Using include produces the same results.
Is there an "easy" way to enable me to call form helpers from the console? I'd prefer to do this without any dependency on controller or view files, if possible.
Peter, I'm happy to solve your problem.
For simple view helpers, it's very easy
> app.helper.link_to 'root', app.root_path
# <a href = ....> # as expected
However your specific case is not that easy as form_for needs view_context and then a controller instance to work.
# Get a controller instance at first. Must do it.
# Without this step `app.controller` is nil
> app.get app.root_path
# Use an instance variable but not variable!
> #user = User.last
# Get the view
# Note you need to take the trouble to define url manually
# because console can only reach path through app instance
> app.controller.view_context.form_for(#user, url: app.user_path(#user)){|f| f.input :name}
# Job done
You need to use include, not extend:
irb(main):007:0> include ActionView::Helpers::FormHelper
=> Object
irb(main):008:0> form_for
ArgumentError: wrong number of arguments (0 for 1)
Related
I have a Ruby on Rails app that uses Devise (and Omniauth) for authentication.
I'm trying to integrate with an iOS app (out of my control) that wants to embed pages from my app. This app needs my pages to have a specific visual appearance, so I want to create an additional set of authentication views.
After digging around in the Devise docs, I've gathered that maybe I need to create a new devise_scope block in routes.rb:
devise_scope :user do
get "iosapp/users/sign_in" => "devise/sessions#iosapp_new"
post "iosapp/users/sign_in" => "devise/sessions#iosapp_create"
delete "iosapp/users/sign_out" => "devise/sessions#iosapp_destroy"
end
And I created a new set of views that correspond to those routes:
app/views/devise/sessions/iosapp_new.html.rb
app/views/devise/sessions/iosapp_create.html.rb
app/views/devise/sessions/iosapp_destroy.html.rb
But loading /iosapp/users/sign_in in the browser leads to a Rails error:
undefined method `errors' for nil:NilClass
That error stems from line 9 of devise_helper.rb (https://github.com/plataformatec/devise/blob/master/app/helpers/devise_helper.rb):
module DeviseHelper
# A simple way to show error messages for the current devise resource. If you need
# to customize this method, you can either overwrite it in your application helpers or
# copy the views to your application.
#
# This method is intended to stay simple and it is unlikely that we are going to change
# it to add more behavior or options.
def devise_error_messages!
return "" if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
...
I'm obviously doing something wrong here, but can't figure out why resource is undefined when called from my "alternate" views. It seems as if I may need to create additional controller methods as well, but I can't find anything in the docs about this.
Am I way off track? Or is there a better way to accomplish my goal than this?
Your new views need form_for resource - do you have that in place? It can't find the errors on the resource if there is no resource, hence the error on nil.
I'm just starting to tinker with extending the rails framework, and as an experiment, I thought I'd add some extra info inside the form_for helper. Specifically, when form_for is called, I'd like to generate an extra h1 tag such as:
# regular form_for <form> opening tag
<h1>Woohoo! It's added!</h1>
# tags fed into form_for via &proc
# form_for close <form> tag
At the moment I've added a /lib file that opens up ActiveRecord::FormHelper and overrides "form for". Needless to say writing out the whole form_for method with just the one added line added is dog ugly...but I can't call super() because, well, instead of inheriting from the method I'd like to super(), I've just overwritten it in /lib.
So, assuming I stubbornly want the functionality to be called via the same form_for tag (instead of, for example extended_form_for), what's the standard way for calling back to the original form_for method I'm overwriting? alias_method_chain? Thought I'd ask before I cement in some potentially lousy practices. If any hardened veterans could give an example I'd be appreciative.
Cheers
You could override form_for in your ApplicationHelper:
module ApplicationHelper
def form_for(*)
content_tag(:h1, "Woohoo! It's added!") + super
end
end
alias_method_chain is by far the simplest way to overwrite the method while still being able to call the original method. So in your lib file you'll want something like this:
def form_for_with_header(...)
form_for_without_header(...)
content_tag(:h1, "Header tag here")
# etc...
end
I have a Rails 2.3.5 application and Im trying to render several Partials from within a Model (i know, i know -- im not supposed to). The reason im doing this is im integrating a Comet server (APE) into my Rails app and need to push updates out based on the Model's events (ex. after_create).
I have tried doing this:
ActionView::Base.new(Rails::Configuration.new.view_path).render(:partial => "pages/show", :locals => {:page => self})
Which allows me to render simple partials that don't user helpers, however if I try to user a link_to in my partial, i receive an error stating:
undefined method `url_for' for nil:NilClass
I've made sure that the object being passed into the "project_path(project)" is not nil. I've also tried including:
include ActionView::Helpers::UrlHelper
include ActionController::UrlWriter
in the Module that contains the method that makes the above "render" call.
Does anyone know how to work around this?
Thanks
We use the render_anywhere gem and have been happy with it.
From the README:
require 'render_anywhere'
class AnyClass
include RenderAnwhere
def build_html
html = render :template => 'normal/template/reference',
:layout => 'application'
html
end
end
Including these two modules should be enough. Maybe you forgot to set default_url_options[:host]? Without it you can use _path helpers, but not _url ones.
Include these modules and check out if it works in irb, maybe it will lead you to right solution.
I'm trying to use delayed_job to update a remote database via xml
In my lib folder I put a file with a class that should do a render_to_text with template.xml.builder, but I get:
undefined method `render_to_string' for #<SyncJob:0x7faf4e6c0480>...
What am I doing wrong?
ac = ActionController::Base.new()
ac.render_to_string(:partial => '/path/to/your/template', :locals => {:varable => somevarable})
I had problems with a undefined helper method then I used ApplicationController
ApplicationController.new.render_to_string
render_to_string is defined in ActionController::Base. Since the class/module is defined outside the scope of the Rails controllers the function is not available.
You are going to have to manually render the file. I don't know what you are using for your templates (ERB, Haml, etc.). But you are going to have load the template and parse it yourself.
So if ERB, something like this:
require 'erb'
x = 42
template = ERB.new <<-EOF
The value of x is: <%= x %>
EOF
puts template.result(binding)
You will have to open the template file and send the contents to ERB.new, but that an exercise left for you. Here are the docs for ERB.
That's the general idea.
Rails 5
render_to_string and others are now available as class methods on the controller. So you may do the following with whatever controller you prefer: ApplicationController.render_to_string
I specifically needed to assign a dynamic instance variable for the templates based on an object's class so my example looked like:
ApplicationController.render_to_string(
assigns: { :"#{lowercase_class}" => document_object },
inline: '' # or whatever templates you want to use
)
Great blog post by the developer who made the rails PR: https://evilmartians.com/chronicles/new-feature-in-rails-5-render-views-outside-of-actions
You could turn your template.xml.builder into a partial (_template.xml.builder) and then render it by instantiating an ActionView::Base and calling render
av = ActionView::Base.new(Rails::Configuration.new.view_path)
av.extend ApplicationController.master_helper_module
xml = av.render :partial => 'something/template'
I haven't tried it with xml yet, but it works well with html partials.
I am trying to test my views with RSpec. The particular view that is causing me troubles changes its appearance depending on a url parameter:
link_to "sort>name", model_path(:sort_by => 'name') which results in http://mydomain/model?sort_by=name
My view then uses this parameter like that:
<% if params[:sort_by] == 'name' %>
<div>Sorted by Name</div>
<% end %>
The RSpec looks like this:
it "should tell the user the attribute for sorting order" do
#Problem: assign params[:sort_for] = 'name'
render "/groups/index.html.erb"
response.should have_tag("div", "Sorted by Name")
end
I would like to test my view (without controller) in RSpec but I can't get this parameter into my params variable. I tried assign in all different flavours:
assign[:params] = {:sort_by => 'name'}
assign[:params][:sort_by] = 'name'
...
no success so far. Every idea is appreciated.
If its a controller test then it would be
controller.stub!(:params).and_return {}
If its a helper test then it would be:
helper.stub!(:params).and_return {}
And its a view test it would be:
view.stub!(:params).and_return {}
If you get warning like below.
Deprecation Warnings:
Using `stub` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` instead. Called from /home/akbarbin/Documents/Office/projects/portfolio/spec/views/admin/waste_places/new.html.erb_spec.rb:7:in `block (2 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.
1 deprecation warning total
Finished in 4.86 seconds (files took 4.72 seconds to load)
You can change it into
allow(view).to receive(:params).and_return({sort_by: 'name'})
That's because you shouldn't be using params in your views.
The best way I see it to use an helper.
<div>Sorted by <%= sorted_by %></div>
And in one of your helper files
def sorted_by
params[:sorted_by].capitalize
end
Then you can test your helpers quite easily (because in helpers tests, you can define the params request.
The easy way is to just do this:
helper.params = {:foo => '1', :bar => '2'}
But in general it's better to be more integration-y and not "stub" values when it's feasible. So I prefer to use controller tests with integrate_views. Then you can specify your params to the get, and test that the entire flow works, from sending params to the controller, to having them processed by the controller, and finally to rendering.
I also generally prefer to pull out view logic into helpers, which can be easier to test.
For instance, say I have a helper called selection_list, which returns a Hash whose "selected_preset" key relies on params[:selected_preset], and defaults to 42 if an empty value is specified for the param.
Here's a controller test where we've called integrate_views (you could of course do the same thing with an actual view test, if you're into that).
describe '#show' do
describe 'selected_preset' do
it 'should default to 42 if no value was entered' do
get :show, :params => {:selected_preset => ''}
response.template.selection_list[:selected_preset].should == 42
This integration test will alert me if some part of this functionality breaks. But I also would ideally like to have some unit tests to help me pinpoint that breakage.
I'll start by having the helper use an instance variable instead of directly accessing params. I'll change the above code by adding a single line directly below the get, as follows:
describe '#show' do
describe 'selected_preset' do
it 'should default to 42 if no value was entered' do
get :show, :params => {:selected_preset => ''}
assigns[:selected_preset].should == 42 # check instance variable is set
response.template.selection_list[:selected_preset].should == 42
Now I also can easily perform a helper unit test:
describe MyHelper do
describe '#selection_list' do
it 'should include the selected preset' do
assigns[:selected_preset] = 3
helper.selection_list[:selected_preset].should == 3
Another method of setting view params:
controller.request.path_parameters[:some_param] = 'a value'