Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to access Rails view helpers #33

Open
mgwidmann opened this issue Apr 10, 2017 · 5 comments
Open

Unable to access Rails view helpers #33

mgwidmann opened this issue Apr 10, 2017 · 5 comments

Comments

@mgwidmann
Copy link

With a js.erb file of the following contents:

const ASSET = "<%= asset_path('my-asset') %>";

The above crashes with the following exception (application name replaced with myapp):

ERROR in ./client/js/constants/AssetConstants.js.erb
Module build failed: Error: Command failed: DISABLE_SPRING=1 /Users/mattwidmann/code/ruby/myapp/bin/rails runner /Users/mattwidmann/code/ruby/myapp/node_modules/rails-erb-loader/erb_transformer.rb __RAILS_ERB_LOADER_DELIMETER__ erb
(erb):24:in `<main>': undefined method `asset_path' for main:Object (NoMethodError)
	from /Users/mattwidmann/.rvm/rubies/ruby-2.3.3/lib/ruby/2.3.0/erb.rb:864:in `eval'
	from /Users/mattwidmann/.rvm/rubies/ruby-2.3.3/lib/ruby/2.3.0/erb.rb:864:in `result'
	from /Users/mattwidmann/code/ruby/myapp/node_modules/rails-erb-loader/erb_transformer.rb:19:in `<top (required)>'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands/runner.rb:60:in `load'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands/runner.rb:60:in `<top (required)>'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands/commands_tasks.rb:123:in `require'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands/commands_tasks.rb:123:in `require_command!'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands/commands_tasks.rb:90:in `runner'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
	from /Users/mattwidmann/.rvm/gems/ruby-2.3.3@myapp/gems/railties-4.2.8/lib/rails/commands.rb:17:in `<top (required)>'
	from /Users/mattwidmann/code/ruby/myapp/bin/rails:9:in `require'
	from /Users/mattwidmann/code/ruby/myap/bin/rails:9:in `<main>'

    at ChildProcess.exithandler (child_process.js:211:12)
    at emitTwo (events.js:106:13)
    at ChildProcess.emit (events.js:191:7)
    at maybeClose (internal/child_process.js:885:16)
    at Socket.<anonymous> (internal/child_process.js:334:11)
    at emitOne (events.js:96:13)
    at Socket.emit (events.js:188:7)
    at Pipe._handle.close [as _onclose] (net.js:501:12)

Same for things like javascript_path, image_path, ect.

I have got this to work with my own rake task separately, heres the code for doing that. The trick is being able to add include Rails.application.routes.url_helpers, which most templating engines do not make very easy (Erubis is terrible as you can see below):

# Copied from https://github.com/rails/rails/blob/master/actionview/test/template/erb/helper.rb
class ViewContext
  include ActionView::Helpers
  include Rails.application.routes.url_helpers

  attr_accessor :output_buffer, :controller

  def protect_against_forgery?() false end
end


filename = './client/js/constants/AssetConstants.js.erb'
result = ActionView::Template::Handlers::Erubis.new(File.read(filename)).evaluate(ViewContext.new)
# `result` is the contents of the file after its been evaluated
@rhys-vdw
Copy link
Collaborator

Hey @mgwidmann, sorry for the slow response. I'm open to adding support for this.

Is it possible to do something like this?

// urls.js.erb

<% include Rails.application.routes.url_helpers %>

export const my_favourite_url = <%= my_favourite_url %>

Or do these helpers need to be included before the template is rendered?

If so then I think explicitly listing helpers at the top of a .erb file makes sense, rather than relying on global inclusion. However if this causes some inconsistency then there are a number of other approaches that we can use... For example:

{
  loader: 'rails-erb-loader',
  options: {
    runner: 'ruby',
    engine: {
      name: 'erubis',
      viewContext: 'MyViewContext' // Name of symbol defined in Rails project
    }
  }
}

Or...

{
  loader: 'rails-erb-loader',
  options: {
    runner: 'ruby',
    engine: {
      name: 'erubis',
      includes: [
        'ActionView::Helpers',
        'Rails.application.routes.url_helpers'
      ]
    }
  }
}

Ideally though we'd be able to hook directly into the Rails API that renders ERB. I haven't looked at this, but if it's an option then we could just set engine to rails and have it automatically grab the same context that Rails from within the project.

@mgwidmann
Copy link
Author

I'd personally like to see the second. I think creating your own view context is too much work.

@rhys-vdw
Copy link
Collaborator

rhys-vdw commented May 16, 2017

Fair enough. I think the ideal would be to see exactly what Rails does when compiling ERB and use the same code path.

This would require knowledge of this part of Rails' code base.

I agree this would be nice to support. I am not personally planning to tackle this problem any time soon. I am, however, willing to give guidance and feedback about the approach taken.

I expect this is something that the folks over at rails/webpacker would be interested in having support to achieve parity with Sprockets.

@alassek
Copy link

alassek commented Dec 9, 2017

I can't speak for ERB or Erubi but Erubis provides #evaluate which performs an instance_eval against a context object you pass in.

https://github.com/kwatch/erubis/blob/3bad74be554caaa34527e7407138d4840a71c4f1/lib/erubis/evaluator.rb#L69-L75

    def evaluate(_context=Context.new)
      _context = Context.new(_context) if _context.is_a?(Hash)
      #return _context.instance_eval(@src, @filename || '(erubis)')
      #@_proc ||= eval("proc { #{@src} }", Erubis::EMPTY_BINDING, @filename || '(erubis)')
      @_proc ||= eval("proc { #{@src} }", binding(), @filename || '(erubis)')
      return _context.instance_eval(&@_proc)
    end

@TheFSilver
Copy link

TheFSilver commented May 31, 2019

Hi,

I had a similar problem and solved it including the helper like this in the
./node_modules/rails-erb-loader/erb_transformer.rb file :

delimiter = ARGV[0]
engine = ARGV[1]
handler = case engine
  when 'erubi'
    require 'erubi'
    Erubi::Engine
  when 'erubis'
    require 'erubis'
    Erubis::Eruby
  when 'erb'
    require 'erb'
    include Rails.application.routes.url_helpers
    ERB
  else raise "Unknown templating engine '#{engine}'"
end

if engine == 'erubi'
  puts "#{delimiter}#{eval(handler.new(STDIN.read).src)}#{delimiter}"
else
  puts "#{delimiter}#{handler.new(STDIN.read).result}#{delimiter}"
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants