rails3 gem: acts_as_something - ruby-on-rails-3

I'm trying to extract some common code into a gem.
I'm thinking that acts_as_somethingis a good strategy for simple re-use.
Is there a good tutorial that discusses this for rails3 gems? I've found several that discuss rails2 (such as http://guides.rubyonrails.org/plugins.html) but that is specific to rails2
here are some of the tutorials/blogs I've already read:
http://guides.rubyonrails.org/plugins.html
http://www.thoughtsincomputation.com/categories/coding-gems-for-rails-3-tutorials (this is an excellent start but doesn't cover the act_as_* issue or controllers)
thanks

UPDATE: I've added a blog post based on this answer, but with much more detail: http://thoughtsincomputation.com/posts/coding-an-acts_as-gem-for-rails-3
--
I'm not aware of another tutorial source off the top of my head, but here are some general tips.
Rails 3 makes use of a really useful feature called Railtie - see http://api.rubyonrails.org/classes/Rails/Railtie.html .
So, if I were implementing an acts_as_* gem, I'd start there. My railtie might look something like:
# lib/acts_as_awesome/railtie.rb
require 'rails'
require 'acts_as_awesome'
module ActsAsAwesome
class Railtie < Rails::Railtie
config.to_prepare do
ApplicationController.send(:extend, ActsAsAwesome::Hook)
end
end
end
and the ActsAsAwesome::Hook code:
# lib/acts_as_awesome/hook.rb
module ActsAsAwesome::Hook
def acts_as_awesome(*args)
options = args.extract_options!
# do the things that make the controller awesome.
include ActsAsAwesome::InstanceMethods
before_filter :an_awesome_filter
end
end
I feel the concepts here are sound and have used similar processes before. Basically, it would tell Rails to execute the to_prepare block once during production and before each request in development (we want that because ApplicationController will be reloaded at those times, potentially wiping out our hook method); and the hook is just that: it adds a hook to all controllers (or rather, all controllers that extend ApplicationController) to allow the user to introduce the real "Awesome" code into their controllers without otherwise affecting controllers that don't need it.
The #acts_as_awesome hook doesn't, in itself, convey the Awesome functionality. That's because not all controllers might need this functionality. Instead, the method is responsible for introducing the real awesome stuff, via the ActsAsAwesome::InstanceMethods module. This way, the user only gets the Awesome functionality if they explicitly call the acts_as_awesome method. It also adds a before filter to the controller to demonstrate that the code in this method would be evaluated exactly the same as if it were in the target controller class itself.
This technique should work exactly the same if you're targeting models instead of controllers: just inject your hook into ActiveRecord::Base. As AR:B is only loaded at Rails boot, you should probably be able to put that into an initializer (refer to the Railtie docs), but I reserve the right to be mistaken here.
A gotcha concerning the railtie: the documentation reads as if it should have been autodetected, but I often have problems with this. To get around it, simply require the railtie from your gem's main source file (in the above example, that would be lib/acts_as_awesome.rb).
You can see the entire ActsAsAwesome source in all its glory at my github account:
http://github.com/sinisterchipmunk/acts_as_awesome
I hope this is helpful. Your question was somewhat high-level so a high-level response is the best I can do.
-Colin MacKenzie IV
http://thoughtsincomputation.com
#sinisterchipmnk

Related

Sorbet fails when using creating an instance of an autoloaded class in rails initializer

In a rails app using sorbet, when you have a method that expects an instance of a type, say Foo. And you need to initialize an instance of Foo in an initializer for the app that persists in memory between requests. And then you make any change to the source code of Foo or any file that Foo uses. Then sorbet believes that this instance is no longer an instance of Foo, and you need to restart your app in order for it to stop erroring.
This seems like a rare case, but in our app, we use a bit of dependency injection, and it's a large team. So sorbet is making us restart our app almost every time we do a git update, and many, many times throughout the day as we write code. We have a large app which takes a while to restart, and it's quite frustrating to have to do this.
Any ideas on how to fix this? If helpful, I could make a sample rails app to demonstrate this behavior.
I ran into a similar issue myself when I was referencing a model in a Rails initializer. I was advised that there are 2 solutions:
Refactor the code so that you don't have this issue
Add checked(:tests) or checked(:never) to the sigs of methods that are having this problem. This would preserve the runtime and test time checks, but eliminate the errors in development. See the link below for documentation
https://sorbet.org/docs/runtime#checked-whether-to-check-in-the-first-place

How I can organize namespace of classes in app/modules with rails?

[This is a follow-up question to "How I can modularize Rails model?"]
Is there anyway to organize classes in app/models directory of Rails?
Do I have to use single top-level namespace for all of the classes?
Initial motivation is, I want place business logic classes which do not inherited from ActiveRecord::Base into app/models directory. Searching this site reveals many answers which recommend to place business logics classes in the app/models directory. I posted a different question, and got recommendation that it is possible to place such classes into lib directory.
Now I'm curious. I'd like to place these classes into different namespace and directory in apps/models directory which recommended by others. Is it possible?
Actually, I experiment that, but it seems to me that is what rails is not expected. If I create such a class (like SomeModName::ClassInMod in some_mod_name/class_in_mod.rb ) it does not get loaded. Also, I defined constants inside the module. Since they're not loaded, I can't use them. Actually, with rspec it work without problem, but with rails server, the class does not loaded. I'm sure it is related to autoloading issue.
In addition to the classes mentioned above, classes inherited from ActiveRecord::Base can be placed into some namespaces inside a module. I'm curious whether this work well or not too.
So the question in other words: can I make rails happy by configuring these files to be loaded, or is not the way rails designed?
Yes, you can define an ActiveRecord class in a module. The easy way is to use the generator:
./script/rails generate model logic/phase_logic
./script/rails generate model logic/evaluation_logic
Observe, that Rails will create additionally a file with the definition of the module. In this case:
# app/models/logic.rb
module Logic
...
end
# app/models/logic/phase_logics.rb
class Logic::PhaseLogic < ActiveRecord::Base
...
end
# app/models/logic/evaluation_logics.rb
class Logic::EvaluationLogic < ActiveRecord::Base
...
end
Your problems with constants defined in the module were caused by the fact, that you defined the constants in the definition module "wrapped" around only one model, from the two you have created. A very important part in understanding ruby (and Rails) - especially for people who have strong background in compiled languages - is to remember that there is no compilation phase, so the definition of some class is read and executed only when that specific class is used. Sometimes a week after the application server has been started and served thousands of requests.
Thus, as I said in the previous question's answer, the problem with autoloading was that sometimes the definition of the constants was read after the definition which was trying to use them. The order was random - if the first used object happened to be EvaluationLogic, then the error emerged. It the first object happened to be PhaseLogic, everything was fine.
Now, when you have a file for the module itself, and define constants in that file (app/models/logic.rb), autoloading will be able to find and execute the definitions before any class starts to use them. I hope everything will be OK.

Implementing Rails 3 template handlers

It appears there's not much documentation on Rails template handlers. There's the included handlers, like RJS, ERB, and Builder, which offer some assistance.
I'm trying to implement my own, and I've succeeded, albeit with a bit of weird code, or possibly there's something I don't quite understand.
class MyHandler < ActionView::Template::Handler
def call(template)
template.source.inspect
end
end
So what's weird is that I have to call inspect, otherwise Rails will try to eval the string as Ruby code.
I was under the impression that that's what include ActionView::...::Compilable did (which I'm not including in my code).
Now, if I make my template "compilable" (by using the include... statement), it still looks for the call method instead of the compile method.
So could anyone explain to me a bit more about how this works?
Thanks!
I've just been going through this problem myself. Basically rails expects the renderer's .call method to return ruby code that will render your template. It then dynamically generates a method which runs this code, and injects it into a module.
The module has all of the url/application helpers included, which means they're in scope for the template.
So, in answer to your question the solution is for .call to return some ruby code that outputs your rendered template as a string, or for it to render ruby code that invokes your template engine.
Check out tilt and temple, I have learnt a lot about template engines reading their code.

Make all ActionViewer helper methods available in a module

I am using ruby on rails 3. I have a model called Content. Within the controller, I extract some variables and pass them to the Viewer. In the Viewer, I would like to use of my self made functions so that I don't have too much logic and code in the Viewer. I defined these functions in the ContentsHelper module.
My functions defined in the ContentsHelper module are making use of ActionViewer helper functions, however these are not available in the ContentsHelper module. I am getting for example NoMethodError: undefined method content_tag' for #<Content:0x1025b9458> error.
How is it possible to make all the ActionViewer helper functions available?
Newbie.
following suggestion of Tim:
module ContentsHelper
include ActionView::Helpers
def bla
content_tag(:h2, self.title)
end
I can run the bla method as Content.bla in the console. This is perfect!
But when I replacec content_tag with image_tag, then I run into trouble and get this error:
NoMethodError: undefined method `config'
from /Users/sonat/.rvm/gems/ruby-1.8.7-p334/gems/activemodel-3.0.0/lib/active_model/attribute_methods.rb:364:in `method_missing'
from /Users/sonat/.rvm/gems/ruby-1.8.7-p334/gems/activerecord-3.0.0/lib/active_record/attribute_methods.rb:46:in `method_missing'
from /Users/sonat/.rvm/gems/ruby-1.8.7-p334/gems/activerecord-3.0.0/lib/active_record/attribute_methods.rb:44:in `send'
I can see that module that is responsible of the image_tag method is missing "something". I would appreciate if you guys tell me the general approach in finding what is missing and how I could solve the problem.
Looks like you're trying to do something odd like to use the helper method inside your model instead of inside your view. Helper methods are for views, not for models.
Added
Tim asked if I could provide some links to other resources, but really this is such basic stuff that I'm not aware of anything dealing with this. Mostly tutorials deal with unusual things like using helpers outside views.
So instead, here's some code:
app/helpers/contents_helper.rb
module ContentsHelper
def foo
content_tag :p, "Foo"
end
end
app/views/contents/index.html.erb
<h1>Listing Contents</h1>
<%= foo %>
The HTML output when going to /contents URL
<h1>Listing Contents</h1>
<p>Foo</p>
And that's as hard as it is.
content_tag:
Does this work?
module ContentsHelper
include ActionView::Helpers
end
image_tag:
Looks like you'll have to make your models know where the images are located by implementing a #path_to_image(image) method.
I've set up a Gist for us to mock out the features you need here:
https://gist.github.com/1015333
You should be able to download and run forNoNameNo.rb (which should run quietly without errors.)
That file has some examples of what's needed to make image_tag work.
further discussion:
Helpers are probably the weirdest part of Rails. They're declared at the controller level, but are only included at the view level, and rely on weird parts of the controller stack for their info; and yet, the info they are mainly concerned with presenting is the model info. So they're spread across all these weird concerns.
To deal with code-organizations issues arising from all of this, people have tended toward the Presenter pattern. (Also see rbates' version)
That said, if you'd prefer to stray off the beaten path in rails, it's normally really useful to read the source code.
If you're using textmate:
mate `gem which actionpack`../action_view
in Terminal.app should get your there. If you're using another editor/operating system you might have to navigate to whereever gem which actionpack tells you to go.

using helper methods in a view in rails 3

there is something that is disturbing me a bit about rails 3.
i am trying to upgrade my application from rails 2 to rails 3.
i followed all the steps necessary to do and things were working well for me till now except one thing:
i have a helper method that is defined differently in different helpers, for example it is defined in the application_helper in one way and the same method defined in a different way in the homepage_helper, and defined differently in another helper, let's say video_helper, in addition to that, it is used in a shared view that is used throughout the application, now if i am in the homepage, i want this function to get called from the homepage_helper and if i am in the video page, the function must be called from the video_helper, based on the concept of convention in rails, this worked perfectly in rails 2 but in rails 3, the function is always called from the last helper, alphabetically ordered, that is in the example, video_helper.
How can i fix this?
Kindly advice.
Appreciate all the help.
In rails 3 all helpers are always loaded.
You can either:
1) name your helpers differently.
2) Explicity define which helpers you want
3) Check the controller from the helpers to determine which helper to provide.
If I'm understanding your post right, you have something like
module ApplicationHelper
def error_messages
...
end
end
module VideoHelper
def error_messages
...
end
end
module HomepageHelper
def error_messages
...
end
end
And now you want to call error_messages from a specific helper and calling error_messages arbitrarily behaves differently from how it did in Rails 2. Am I right so far? If not, you can ignore the rest of this post =P
I just spent half an hour playing with the syntax and one of my projects in irb and the closest I could get was something along the lines of Posts::PostsHelper.helper_method, which works in irb if I 'include PostsHelper' first.
With that said, if you're willing to do something like Class::ClassHelper.helper_method, why not just rename "helper_method" so that it's unique to your ClassHelper? You're going to have to change each "helper_method" call to be specific to some helper anyways. I think this is a much cleaner solution