ruby-grape / grape

An opinionated framework for creating REST-like APIs in Ruby.
http://www.ruby-grape.org
MIT License
9.88k stars 1.22k forks source link

Namespace is evaluated twice #2081

Open sashazykov opened 4 years ago

sashazykov commented 4 years ago

I put the following code into my namespace definition:

puts caller
puts '~~~~'

and it is executed twice, I see two backtraces. Is this normal? The file itself is loaded only once.

/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `instance_eval'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `block in evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/util/lazy_block.rb:11:in `evaluate_from'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:127:in `evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `block in nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:172:in `block in namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/settings.rb:160:in `within_namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:169:in `namespace'
/opt/reports_data/app/controllers/data.rb:50:in `block in <class:Data>'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `instance_eval'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `block in evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/util/lazy_block.rb:11:in `evaluate_from'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:127:in `evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `block in nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:172:in `block in namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/settings.rb:160:in `within_namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:169:in `namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:155:in `replay_step_on'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:147:in `block in add_setup'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:146:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:146:in `add_setup'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:42:in `block (2 levels) in override_all_methods!'
/opt/reports_data/app/controllers/data.rb:37:in `<class:Data>'
~~~~
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `instance_eval'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `block in evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/util/lazy_block.rb:11:in `evaluate_from'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:127:in `evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `block in nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:172:in `block in namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/settings.rb:160:in `within_namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:169:in `namespace'
/opt/reports_data/app/controllers/data.rb:50:in `block in <class:Data>'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `instance_eval'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:120:in `block in evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/util/lazy_block.rb:11:in `evaluate_from'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:127:in `evaluate_as_instance_with_configuration'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `block in nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api/instance.rb:107:in `nest'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:172:in `block in namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/settings.rb:160:in `within_namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:169:in `namespace'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:155:in `replay_step_on'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:105:in `block in replay_setup_on'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:104:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:104:in `replay_setup_on'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:97:in `mount_instance'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:86:in `block in mount'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:84:in `each_pair'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/dsl/routing.rb:84:in `mount'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:155:in `replay_step_on'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:147:in `block in add_setup'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:146:in `each'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:146:in `add_setup'
/usr/local/bundle/gems/grape-1.3.3/lib/grape/api.rb:42:in `block (2 levels) in override_all_methods!'
/opt/reports_data/app/controllers/api.rb:29:in `<class:API>'
~~~~
sashazykov commented 4 years ago

1) it is evaluated in /opt/reports_data/app/controllers/data.rb:37 when the controller code is loaded. 2) also evaluated in /opt/reports_data/app/controllers/api.rb:29 when the controller is mounted with mount Controllers::Data

dnesteryuk commented 4 years ago

@alexandrz I agree it looks weird :disappointed:

require 'grape'

class AddressAPI < Grape::API
  params do
    requires :test, type: String

    puts 'Address API'
  end
  post '/address' do
  end
end

class API < Grape::API
  version 'v1', using: :path

  mount AddressAPI
end

Address API will be printed 2 times. The reason is here where every method call gets added to the setup variable. Considering AddressAPI, the setup variable will contain, params and post:

[
  {method: 'params', args: args, block: block},
  {method: 'get', args: args, block: block}
]

Then when AddressAPI gets mounted all method calls stored in the setup variable gets replayed on API there.

There is a memory leak issue related to this approach.

You might consider a native Ruby include method instead, more info there https://nesteryuk.info/2020/04/12/grape-include-is-still-better-for-code-splitting.html. It is cheaper, our project uses this approach for 6 years without any issues.