How Rails understands what request is? - ruby-on-rails

So, I wrote this code:
module UniversityFinder
def define_shortcut_part
r=request
subs=request.subdomains
if subs.length>1
subs[1]
else
subs[0]
end
end
def university
university=University.find_by_shortcut(define_shortcut_part)
end
end
Which suits my one needs, in separate file, and included module in one of my controllers.
The thing is, that I expected uninitialized constant error from Rails for this request variable.
On the opposite, it works fine and does what I wanted.
This is purely curiosity question regarding Rails source code ('rails magic') for better understanding. This variable nor even called like instance variable (#request), but still Rails gets this local. Can someone explain how it works?

Related

Namespaced class references inside other classes generate uninitialized constant errors

in my Rails app I'm making liberal use of class namespacing like so:
class Person
class PrimaryEmailAddress
class Update < Trailblazer::Operation
def persist(options, **)
Shared::Property::HasOne::Update.(
{property_class: PrimaryEmailAddress, property_value: #params[:email_address]},
'current_user' => self['current_user'], parent_node: options[:person])
end
end
end
end
Unfortunately, the ruby interpreter keeps thinking that my namespaced, embedded functions are part of other class' namespace, and it throws uninitialized constant errors. i.e. at runtime I get an error like this
uninitialized constant Person::Shared::Property
Basically, the interpreter is looking at this function Shared::Property::HasOne::Update and treating it like it's in the Person namespace, when it's not, and then throwing an error.
I can get around the problem by explicitely stating that the function is in the Object namespace, like so Object::Shared::Property::HasOne::Update, but adding Object:: all over the place is annoying and ugly. Is there a better solution anyone knows of? Short of rewriting all of my class names / namespacing.
I imagine part of the problem is that Person::Shared is a valid namespace, and so the ruby interpreter starts treating the Shared::Property:: ... reference like I just forgot to add Person to the beginning.
I really appreciate any feedback!!
Found the answer: by adding :: in front of a class reference, I force the ruby interpreter to look at the top level namespace. i.e. instead of Object::Shared::Property::HasOne::Update I can do ::Shared::Property::HasOne::Update, which I find more readable.
While there are a fair number of questions about uninitialized constant problems, I had trouble finding this answer because all the questions I found were framed in specific cases, rather then being genericized. It seems likely that this is a duplicate question and I just haven't found the other one, but I'm going to post this QA here in case I'm wrong and this helps someone else running into this same problem.
This question ended up leading me to the correct answer.

Unable to initialize class in Rails App

Directory:
Prototype
-app
-assets
-controllers
---welcome_controller.rb
domainobjects
---SimilarJob.rb
Utilities
--API.rb
Controller Code
require_relative '../domainobjects/SimilarJob'
class WelcomeController < ApplicationController
def index
foo = API.new('DEVKEY')
res = foo.RetrieveFacts("Test", "Me")
#newResult = SimilarJob.new("test") <-- Failing Line!!!
render :text => res["Response"]["IsInternationalResponse"]
end
end
Object Code
class SimilarJob
end
I stripped out some things, but the API class exists in a separate directory, "Utilities", and for some reason I don't even have to reference it using the "requires_relative" keyword. It's a wrapper class that includes HTTParty and makes a successful GET request to my external API every time. Can someone explain why I seemingly don't have to reference it anywhere?
Alternatively, attempting to initialize the SimilarJob class fails each time. The error is:
uninitialized constant WelcomeController::SimilarJob
From what I researched here and on the web, this means I'm not referencing the file correctly. To test this out, I tried naming it incorrectly in the "requires_relative" statement and the framework informs me that the requested file could not be loaded. So it seems like Rails is finding my class, it just won't initialize it for some reason.
The most maddening part is that I'll make a few small changes to SimilarJob, restart my server, and it'll work all of a sudden. If I stop and start the server again, it's back to the error I pasted below.
This is my first time really digging in something other than .NET MVC or KnockoutJS..would you guys mind pointing out the error of my ways?
EDIT: I used the generate command for this controller, so all views and routes work appropriately. In fact, if I comment out the problematic line, the property I'm referencing on the last line in my JSON response renders to the file just fine.
EDIT v2: Strangely enough..changing my class name to Jobs (one word) is getting rid of this error. This comes off as bizarre! Can anyone confirm that this is my issue?
Names matter, and you've named your file wrong. SimilarJob.rb needs to be similar_job.rb.
Similarly, your API file should be called api.rb, and the class it defines should be called Api. This stuff is important, as you've deviated badly from Rails convention, and are suffering for it.

How to access a controller constant in an rspec test under Rails 3

Using Rails 3.2 I have a controller in a subdirectory (e.g. /controllers/data_feeds/acme_feed_controller.rb)
This controller has some constants as below
class DataFeeds::AcmeFeedController < ApplicationController
MY_CONSTANT = "hi
def do_something do
...
end
end
In my rspec controller spec (which is in /spec/controllers/data_feeds/acme_feed_controller_spec.rb) I want to access that constant and below are two ways I've tried it (both commented out in the code below)
describe AcmeFeedController do
if "tests something" do
#c = AcmeFeedController.MY_CONSTANT
#c = DataFeeds::AcmeFeedController.MY_CONSTANT
end
end
I'm clearly not understanding something about the scope in which the spec test is run. What do I need to do and equally important why (i.e. what's happening with the scopes).
Thanks for your help.
Constants cannot be referenced with dot syntax, so DataFeeds::AcmeFeedController.MY_CONSTANT would never work in any context. You need to use :: to reference constants: DataFeeds::AcmeFeedController::MY_CONSTANT.
Note that is a ruby issue and has nothing to do with RSpec. When you face an issue like this, I recommend you figure out how to do it with plain ruby (e.g. in IRB) before worrying about how it works in RSpec (usually it will be the same, anyway).
If you want to know how constants work in ruby, I commend you watch this talk that explains them in detail.
Also, you can do this without repeating controller class name spaces.
describe AcmeFeedController do
if "tests something" do
c = controller.class.const_get('MY_CONSTANT')
end
end
This kind of trick may not be approved in application codes, but in tests it may be.

Using Ruby alias to extend a Gem

This is more a theoretical question, but I am curious anyway. I am a ruby / ruby on rails newbie (but with a lot of ancient experience in other languages / frameworks) so this is mainly a curious / learning question. Thanks in advance for any help!
I thought I could do a quick extension to a ruby gem using alias as follows:
module InstallMyExtension
def self.included(base)
base.class_eval {
alias :some_method_in_gem_without_my_extension :some_method_in_gem
alias :some_method_in_gem :some_method_in_gem_with_my_extension
}
end
def some_method_in_gem_with_my_extension
debugger
# ... do fun stuff here
some_method_in_gem_without_my_extension
end
end
Then in some initialization file I do:
Foo::SomeControllerInFoo.send :include, InstallMyExtension
I learned this technique in the Radiant CMS where its used all over the place to extend base behavior. I understand this technique is now disapproved of, but it seemed like a quick way to just try some ideas out, before forking a branch on the gem, etc, etc
First off is there a better way in Rails 3 to do a quick hack extension like this (which might be useful just to test a theory, before forking the gems etc???)
Second off, its not working, and there are multiple things I don't understand
Then let me explain the weirdness I am seeing:
Even if I do do the the "include" as shown above, when I go into the console I see some really weird behavior, that I don't understand:
1) I type Foo::SomeControllerInFoo i get back Foo::SomeControllerInFoo as I would expect. HOWEVER if run the same exact expression a second time, Foo::SomeControllerInFoo comes back undefined!
2) Just to play around I did foo = Foo::SomeControllerInFoo, and then I can do foo.send, foo.methods, whatever I like, but only if I save the copy of the class in foo! What's with that?
3) If I now do foo.send :include, MyExtension the behavior within the debug console is as expected (i.e. the original class contained in the gem now has my behavior added to it.) HOWEVER running the same code during initialization has no effect. Nothing breaks, but the controller is not extended.
Weird that it doesn't work, I just tried again to be sure and that does the trick (put this code in a file within config/initializers).
I always use a shortcut:
alias_method_chain :some_method_in_gem, :my_extension
instead of the two aliases lines, but it's exactly the same.
You could overwrite some methods much more easily using class_eval directly. Again in an initializer:
Foo::SomeControllerInFoo.class_eval do
def some_method_in_gem
#your redefinition
end
end
Sorry but no added value for your other questions: seems just really weird and buggy.
Just to be sure, when you want to run the method defined in your controller, do:
c = Foo::SomeControllerInFoo.new
c.method_name

Overriding default Rails date_select

So what I'd like to do is to override the default date_select method (I'd like to make an 'optional / unspecified' date input). What I've tried so far is this:
lib/overrides.rb
ActionView::Helpers::DateHelper::DateTimeSelector.class_eval do
def build_selects_from_types(order)
select = ''
order.reverse.each do |type|
separator = separator(type) unless type == order.first # don't add on last field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select.insert(0, '<p>HI!!</p>') # or whatever...
select.html_safe
end
end
I then required 'overrides' at the bottom of environment.rb but when starting WEBrick I get this error:
~/.rvm/gems/ruby-1.9.2-p0/gems/activesupport-3.0.0/lib/active_support/dependencies.rb:479:in
`load_missing_constant':
ActionView::Helpers is not missing
constant DateTimeSelector!
(ArgumentError)
So I obviously don't really know what I'm doing but this seems like a reasonable thing to attempt at least.
The error above seems to imply that it can't find the DateTimeSelector class but I've peered at the code in ~/.rvm/gems/ruby-1.9.2-p0/gems/actionpack-3.0.0/lib/action_view/helpers/date_helper.rb and I think I've got the module hierarchy right. Is it because it's a private Rails class?
Any thoughts are most welcome :)
In Ruby doesn't exist the concept of private class. Classes are never private.
The reason for the error is because the path is invalid. It should be
ActionView::Helpers::DateTimeSelector
not
ActionView::Helpers::DateHelper::DateTimeSelector
BTW, what you are trying to do is absolutely a bad idea. The fact that Ruby gives you the power of reopening classes and "patch" methods, doesn't mean you should do this for such this kind of customizations.
You should never make these chances to the Rails codebase unless you really know what you are doing. The risk is to break things that depends on this method.
The right way to go is do define a new helper and build your own logic.

Resources