Class and module scope in rails helpers - ruby-on-rails

I'm trying to write a rails app that creates an object in the controller based on a helper module, which is written below:
module StockPricesHelper
require 'net/http'
class Stock
attr_accessor(:data)
def initialize(stock)
#url = "http://finance.yahoo.com/d/quotes.csv?s=#{stock}&f=sb2b3jk"
end
def download_data
#data = NET::HTTP.get_response(URI.parse(#url)).body
end
def clean_string
#data = #data.strip
end
def db_format
1
end
end
end
I get an error uninitialized constant StockPricesHelper::Stock::NET from the rails server.
Am I correctly putting this in a helper module?
What am I doing wrong? I think I'm off on the scope but I don't know where.

You have misspelled the "NET" module. It is Net. (Ruby is case sensitive)
Rails helpers are intended to be view helpers, i.e. aid in generating HTML.
It looks like you are performing something which would be better placed in a controller or background job.

Related

Rails: Make Route Helper Methods Available to PORO

Within a Plain Old Ruby Object (PORO) in my rails app: I have the following method:
def some_method
content_tag(:li, link_to("Do something", somewhere_path(object.id)))
end
First: the object didn't understand the method content_tag, so I added the following which made the object understand that method:
include ActionView::Helpers::TagHelper
Then the object didn't understand link_to so I added the following which made the object understand that method:
include ActionView::Helpers::UrlHelper
Now, it doesn't understand my route: somewhere_path(object.id).
Question: How can I make the PORO in my rails app understand the helpers which generate routes?
Followup Question: Is there an easier way to include all of this functionality into my PORO object? Perhaps there is a way to only include one major module and get all of this functionality (as opposed to perhaps needing to require 3 different modules).
You either have to do what you describe in your self-answer (link to revision I refer to), or inject some context into your POROs. Where context is something which knows all those methods. Something like this:
class ProjectsController
def update
project = Project.find(params[:id])
presenter = Presenters::Project.new(project, context: view_context) # your PORO
# do something with presenter
end
end
And your PORO would look like this:
module Presenters
class Project
attr_reader :presentable, :context
def initialize(presentable, context:)
#presentable = presentable
#context = context
end
def special_link
context.somewhere_path(presentable)
end
end
end
Me, I like neither of them. But sometimes we have to choose a lesser evil.
If anyone happens to know of a current way to get access to all of these methods with one include statement then let me know.
Why, yes. There is a way.
module MyViewCompatibilityPack
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
def url_helpers
Rails.application.routes.url_helpers
end
end
class MyPoro
include MyViewCompatibilityPack
...
end
The issue is that actionview-related methods are not available to POROs.
In order to get all the great stuff from actionview: you need to utilize the view_context keyword. Then: you can simply call upon actionview-related methods from your view_context:
class BuildLink
attr_accessor :blog, :view_context
def initialize(blog, view_context)
#blog = blog
#view_context = view_context
end
def some_method
content_tag(:li, link_to(“Show Blog“, view_context.blog_path(blog)))
end
end
So for example: from your controller you would call upon this PORO like so:
BuildLink.new(#blog, view_context).some_method
For more information, see below references:
Rails doc on view_context
Utilization of view_context via presenter pattern, shown in this article
Railscast which talks through utilizing view_context via presenter pattern

rails 4, change default form builder globally

I have sub-classed the default form builder to add some additional methods. Sample code is below.
module ApplicationHelper
class AppFormBuilder < ActionView::Helpers::FormBuilder
def coordinates_field(method, options = {})
options[:readonly] = 'true'
....
#template.text_field(#object_name, method, objectify_options(options))
end
end
end
This works well, but to use it I have to change the view code for every form that uses the coordinates_field method, i.e.,
<%= form_for #object, :builder => ApplicationHelper::AppFormBuilder do |f| %>
...
<% end %>
It sounds like it is theoretically possible to change the default form builder globally (config.action_view.default_form_builder), but I can't seem to get this to work. This is what I have tried in /config/application.rb:
module Web
class Application < Rails::Application
...
config.action_view.default_form_builder = "ApplicationHelper::AppFormBuilder"
end
end
Which results in error undefined method 'new' for "ApplicationHelper::AppFormBuilder":String when I hit a view that has a form.
If I instead try this
config.action_view.default_form_builder = ApplicationHelper::AppFormBuilder
I get the error *config.action_view.default_form_builder = ApplicationHelper::AppFormBuilder* when the application starts.
Can anyone provide guidance on how to get this to work?
As mentioned in the official docs, in Rails 5 the correct way is to specify it in a controller. To make it application wide, just set in your ApplicationController:
class ApplicationController < ActionController::Base
default_form_builder AppFormBuilder
end
This makes much more sense. FormHelpers are part of the view layer and not the application config.
ApplicationHelper::AppFormBuilder is not required yet at the time application.rb loads. You can try to put this in a separate initializer file (create one in config\initializers):
module Web
class Application
ActionView::Base.default_form_builder = AppFormBuilder
end
end
I like Max's answer, but (rails n00b here so YMMV) I believe this is equivalent and cleaner, directly in config/application.rb:
config.after_initialize do
ActionView::Base.default_form_builder = MyCustomHelper::MyCustomFormBuilder
end
obviously you replace the names MyCustomHelper and MyCustomFormBuilder.
took me about 48hrs to figure this out, thanks to all that well-structured rails documentation.
You should be able to set this from an initializer as you would for other config options. Create a default_form_builder.rb file under config/initializers/. The syntax should simpler than in #Max's answer.
Rails.application.config.action_view.default_form_builder = AppFormBuilder
I suggest you do not include this in a helper. Add it as separate class inside the /lib directory. You may or may not need to prefix the with the module it's contained within.
Finally, you can set this globally from config/application.rb, but you would have to pass it as a string since the class may not be loaded when rails starts up.
config.action_view.default_form_builder = 'AppFormBuilder'
Rails 5+ 👌
class ApplicationController < ActionController::Base
default_form_builder AppFormBuilder
end
Rails 4 🏖
class ApplicationController < ActionController::Base
ActionView::Base.default_form_builder = AppFormBuilder
end
... or if you prefer an initializer, I'd recommend setting it as a string instead, that way you will not have to restart Rails every time you change something in your form builder.. 😴 (since the class may not be loaded when rails starts up. https://stackoverflow.com/a/27992240/2037928)
module Web
class Application
ActionView::Base.default_form_builder = "AppFormBuilder"
end
end

How does including a module work inside a controller?

If I do:
rails generate scaffold account/user username
I get a controller that looks like this:
class Account::UsersController < ApplicationController
def index
#account_users = Account::User.all
end
...
end
If I include the Account Module, then it looks like all the database calls don't need to be prefixed with "Account::". I.e.
class Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this works because I included the Account Module above
end
...
end
Now if I were to move my
controllers/account/users_controller.rb
file to:
controllers/admin/account/users_controller.rb
The file looks like this (note: I also corrected my routes file after this move):
class Admin::Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this call does not work now
end
...
end
But I get an error saying "uninitialized constant Admin::Account::UsersController::User"
It looks like rails is trying to make a database call on the "User" model without the "Account::" module in front of it.
So how does including modules in controllers work? Why does this not work when I move my controller into a different file (and leave the model in the same location from the generated scaffold) but it works with the scaffold generated files? How can I fix this issue?
Resolving the name of a module is done relative to the current module. Try and change it to:
include ::Account
or
include ::Admin::Account
(depending on the module in which your User model is defined)
This will tell ruby to look in the global namespace for the module Account
I guess I didn't realize you can just explicitly require the path to the module you would like to include. I learned this after reading up on modules some more...
So adding an explicit call to "require 'account/user'" just outside the controller class makes it so including the module in the controller works.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

Rails 2.3.5: How does one access code inside of lib/directory/file.rb?

I created a file so I can share a method amongst many models in lib/foo/bar_woo.rb. Inside of bar_woo.rb I defined the following:
module BarWoo
def hello
puts "hello"
end
end
Then in my model I'm doing something like:
def MyModel < ActiveRecord::Base
include Foo::BarWoo
def some_method
Foo::BarWoo.hello
end
end
The interpreter is complaining that it expected bar_woo.rb to define Foo::BarWoo.
The Agile Web Development with Rails book states that if files contain classes or modules and the files are named using the lowercase form of the class or module name, then Rails will load the file automatically. I didn't require it because of this.
What is the correct way to define the code and what is the right way to call it in my model?
You might want to try:
module Foo
module BarWoo
def hello
puts "hello"
end
end
end
Also for calling you won't call it with Foo::BarWhoo.hello - that would have to make it a class method. However includeing the module should enable you to call it with just hello.
Files in subdirectories of /lib are not automatically require'd by default. The cleanest way to handle this is to add a new initializer under config/initializers that loads your library module for you.
In: config/initializers/load_my_libraries.rb Pick whatever name you want.
require(File.join(RAILS_ROOT, "lib", "foo", "bar_woo"))
Once it has been require'd, you should be able to include it at will.
The issue is twofold.
You need to use the outer Foo scope to define BarWoo
You have defined hello as an instance method, then tried to call it on the class.
Define your method using def self.hello instead of def hello
module Foo
module BarWoo
def self.hello
puts "hello"
end
end
end
You can also do
module Foo::Barwoo; end;

Resources