Rails: Extending existing code - ruby-on-rails

I'm a PHP developer and have worked extensively with Laravel. However, I currently need to make small extension to Redmine (a Ruby Issue Tracker Tool) for work.
I'm brand new to Ruby and Rails, so I'm simultaneously trying to get up to speed on the language and the framework.
In general, I'll need to make some migrations which add a few columns to Redmines existing table. Then when various methods are trigged in Redmine (logging time entries, deleting entries, creating projects, etc), I'll need to make a couple API calls, and insert/update the returned data in said columns.
So not terribly complicated, however I'm wondering a few things as I get off the ground:
1) Because I'm extending an existing Rails app, should I be creating a Plugin? or a Gem? It seems Redmine has a 'plugin generator' that provides some boiler plate
2) I'll need to hook into existing Save and Update events in Redmine. From what I understand, you're not meant to override existing Controllers and Models. In that, what methods are used for implementing additional functionality to an existing application?
I found this helpful piece: http://www.redmine.org/projects/redmine/wiki/Plugin_Internals
However, it mentions:
As explained above: you rarely want to override a model/controller. Instead you should either:
1) add new methods to a model/controller or
2) wrap an existing method.
Presumably, you wouldn't be adding methods directly to the original source? I notice he uses Modules to implement this, but unsure of exactly how they work.

Yes, original source modification is not recomended because of:
Merge problems when You are updates Redmine
Problems with other plugins
For add new or modify existing methods You must create controller, model or helper patch:
require_dependency 'issues_controller'
module IssuesControllerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :some_method, :your_action # modify some_method method by adding your_action action
end
module InstanceMethods
# modified some_method
# You can call original method before or after
# even in the middle of your actions
# or not to call to all
def some_method_with_your_action # modified some_method
do_something_before # your actions before
some_method_with_your_action # call original some_method if needed
do_something_after # your actions after
end
# new method
def your_method
do_something
end
end
end
IssuesController.send :include, IssuesControllerPatch
And add
require 'path/to/your/issues_controller_patch'
to your_plugin/init.rb
Also, if You want call your code in the middle of original code, You must use hooks. Find nessecary hook in original code (controller, view, helper, model), they looks like this:
call_hook(:controller_account_success_authentication_after, {:user => user})
If not found suitable hook, You can add your own (still have modify original code) or add issue at Redmine page (will have to wait a long)
To use hooks, add hook listener like:
class IssuesControllerHookListener < Redmine::Hook::ViewListener
# use view hook - add path/to/your/view.html.erb redmine issues list
# position of your additions depends of used hook position
# view_issues_index_bottom is hook name
# :partial is parameter, value of that is your view
render_on :view_issues_index_bottom, :partial => 'path/to/your/view'
# use controller hook - call your code inside original
# controller_issues_ready_before_index is hook name, method must be named same
# context here is parameters come from hook calling method
# You can use it for your own purposes
def controller_issues_ready_before_index(context = {})
if context[:some_context_param] == some_value
do_something
end
end
end
And add
require 'path/to/your/hook'
to your_plugin/init.rb

Related

Dynamically created method? _one_time_conditions_valid_718?

I'm new to RoR and jumping into a big RoR project. I used railroady to create a diagram of all of the models and controllers. I've noticed that many of the controllers begin with as many as five methods in the form
_one_time_conditions_valid_XXX?
where XXX ranges from 200 to 1116. However, these methods don't appear in the actual code. Are these methods automagically generated by some gem? I can't find a reference to this for anywhere.
Okay so here is your Reference. These methods are defined in ActiveSupport::Callbacks::Callback in a method called #_compile_per_key_options Line 159. It looks like this
def _compile_per_key_options
key_options = _compile_options(#per_key)
#klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def _one_time_conditions_valid_#{#callback_id}?
true if #{key_options}
end
RUBY_EVAL
end
It is then used for supplying data to the before, after and around filters through 2 different methods called #start and #end.
Both #start and #end check for these methods like so
return if key && !object.send("_one_time_conditions_valid_#{#callback_id}?")
From what it looks like the whole purpose of these methods is simply to determine if a callback as been defined and then if so compile the appropriate hooks.
These hooks are about as clear as their names. before hooks run before the defined action and access the data before the action gets it, after hooks run after the defined action and access the data after the action gets it, and around hooks wrap an action and triggers the event they yield. You can even define your own such as:
class Record
include ActiveSupport::Callbacks
define_callbacks :magic
def magic
run_callbacks :magic do
puts "Abracadabra"
end
end
end
class Magician < Record
set_callback :magic, :before, :perform
def perform
puts "The Magician will now perform a Trick"
end
set_callback :magic, :after do |object|
puts "Tada"
end
end
This is clearly shown by
magician = Magician.new
magician.magic
#Output:
# The Magician will now perform a Trick #(before callback)
# Abracadabra #(actual event)
# Tada #(after callback)
This means if your controllers have "as many as five" of these that there are an equal amount of filters in the form of something like before_filter, after_filter, before_action, around_action, etc. (the list of available callbacks is pretty long)

Ruby 1.9.3 -> 2.0 alias_method and extend

I'm trying to upgrade a Ruby 1.9.3 app to 2.0, and everything seems to have gone smoothly except for one hiccup. I wrote a module which I include in my models to override activerecord destroy. It aliases the existing destroy method to destroy! and then overrides destroy to change a deleted_at timestamp on the record. Only when I upgrade to ruby 2.0 destroy! no longer destroys the record, but behaves just like my new override method. Any idea why this would be? The more relevant section of code is below. Full gist here.
def self.included(base)
base.class_eval do
alias_method :destroy!, :destroy
alias_method :delete!, :delete
default_scope -> { where(:deleted_at => nil) }
end
base.send :extend, ClassMethods
base.send :include, InstanceMethods
end
Check out the paranoia gem. It's a Rails 3/4 compatible implementation of soft deletes that does just what you're after. If all you want to do is provide a soft-delete then I'd use the gem and be done with it. If you want to implement soft deletes yourself, then the implementation can give you some insights into how it's been done before.
If you alias a method that is not directly defined in the current class, then alias looks for the method in the nearest ancestor of the class in which it was executed.
When you include Trashable::InstanceMethods into one of your models, it gets inserted at the front of that model's ancestor chain. Hence, calling destroy! in that model triggers the destroy method on Trashable::InstanceMethods.
If you move def destroy from InstanceMethods to base.class_eval, then it would be defined in the including model directly, and the nearest ancestor of that model that contains 'destroy' would be the relevant module in ActiveRecord. Therefore calling destroy! would trigger an SQL DELETE as expected.
See class.ancestors to further explore this behavior.
In Ruby 2.0 they introduced the concept of prepending modules, so you can insert behaviour between your model and ActiveRecord::Base. I would suggest moving your code into a module and instead of including that model, you can prepend it. Saves from aliasing methods around.
Here are some articles related to the new prepend functionality:
https://gist.github.com/mattetti/5104790
http://blog.crowdint.com/2012/11/05/3-killer-features-that-are-coming-on-ruby-2-0.html
http://dev.af83.com/2012/10/19/ruby-2-0-module-prepend.html

rails application design: static methods

Which would be the most elegant way to define static methods such as "generate_random_string", "generate_random_user_agent", which are called from different libraries?
What are the best practices?
Best practice as I've seen would include:
Put them in a module in /lib/
Include them as mixins in the rest of your application code.
Make sure they are thoroughly tested with their own rspecs (or whatever test tool you user).
Plan them as if you may at some point want to separate them out into their own gem, or potentially make them available as a service at some point. That doesn't mean design them as separate services from the beginning, but definitely make sure they have no dependencies on any other code in your application.
Some basic code might be something like:
module App::Services
def generate_random_string
# ...
end
def generate_random_user_agent
# ...
end
end
Then in your model or controller code (or wherever), you could include them like this:
class MyModelClass < ActiveRecord::Base
include App::Services
def do_something_here
foo = random_string
# whatever...
end
def random_string
generate_random_string
end
end
Notice I isolated the generate_random_string call in its own method so it can be used in the model class, but potentially be switched out for some other method easily. (This may be a step more than you want to go.)

Rails: macro style functions

In models and controllers, we often use Rails macros like before_validation, skip_before_filter on top of the class definition.
How is this implemented? How do I add custom ones?
Thanks!
They're just standard Ruby functions. Ruby's flexible approach to syntax makes it look better than it is. You can create your own simply by writing your method as a normal Ruby function and doing one of the following:
putting it somewhere that's accessible by your controllers such as application.rb
putting it in a file and requiring it in.
mixing the code into a class via the Ruby include keyword.
That last option is great for model classes and the first option is really only for controllers.
An Example
An example of the first approach is shown below. In this example we add code into the ApplicationController class (in application.rb) and use it in the other controllers.
class BusinessEntitiesController < ApplicationController
nested_within :Glossary
private
# Standard controller code here ....
The nested_within provides helper functions and variables to help identify the id of the "parent" resource. In effect it parses the URL on the fly and is accessible by every one of our controllers. For example when a request comes into the controller, it is automatically parsed and the class attribute #parent_resource is set to the result of a Rails find. A side effect is that a "Not Found" response is sent back if the parent resource doesn't exist. That saves us from typing boiler plate code in every nested resource.
That all sounds pretty clever but it is just a standard Ruby function at heart ...
def self.nested_within(resource)
#
# Add a filter to the about-to-be-created method find_parent_id
#
before_filter :find_parent_id
#
# Work out what the names of things
#
resource_name = "#{resource.to_s.tableize.singularize}"
resource_id = "#{resource_name}_id"
resource_path = "#{resource.to_s.tableize}_path"
#
# Get a reference to the find method in the model layer
#
finder = instance_eval("#{resource}.method :find_#{resource_name}")
#
# Create a new method which gets executed by the before_filter above
#
define_method(:find_parent_id) do
#parent_resource = finder.call(params[resource_id])
head :status => :not_found, :location => resource_path
unless #parent_resource
end
end
The nested_within function is defined in ApplicationController (controllers/application.rb) and therefore gets pulled in automatically.
Note that nested_within gets executed inside the body of the controller class. This adds the method find_parent_id to the controller.
Summary
A combination of Ruby's flexible syntax and Rail's convention-over-configuration makes this all look more powerful (or weirder) than it actually is.
Next time you find a cool method, just stick a breakpoint in front of it and trace through it. Ahh Open Source!
Let me know if I can help further or if you want some pointers on how that nested_within code works.
Chris
Chris's answer is right. But here's where you want to throw your code to write your own:
The easiest way to add Controller methods like that is to define it in ApplicationController:
class ApplicationController < ActionController::Base
...
def self.acts_as_awesome
do_awesome_things
end
end
Then you can access it from individual controllers like so:
class AwesomeController < ApplicationController
acts_as_awesome
end
For models, you want to reopen ActiveRecord::Base:
module ActiveRecord
class Base
def self.acts_as_super_awesome
do_more_awesome_stuff
end
end
end
I personally would put that in a file in config/initializers so that it gets loaded once, and so that I know where to look for it always.
Then you can access it in models like so:
class MySuperAwesomeModel < ActiveRecord::Base
acts_as_super_awesome
end

How to call expire_fragment from Rails Observer/Model?

I've pretty much tried everything, but it seems impossible to use
expire_fragment from models? I know you're not supposed to and it's
non-MVC, but surely there much be some way to do it.
I created a module in lib/cache_helper.rb with all my expire helpers,
within each are just a bunch of expire_fragment calls. I have all my
cache sweepers setup under /app/sweepers and have an "include
CacheHelper" in my application controller so expiring cache within the
app when called via controllers works fine.
Then things is I have some external daemons and especially some
recurring cron tasks which call a rake task that calls a certain
method. This method does some processing and inputs entries into the
model, after which I need to expire cache.
What's the best way to do this as I can't specify cache sweeper within the model.
Straight up observers seem to be the best solution but then it
complains about expire_fragment being undefined etc etc, I've even
tried including the ActionController caching classes into the observer
but that didn't work. I'd love some ideas of how to create a solution
for this. Thanks.
Disclaimer: My rails is a bit rusty, but this or something like it should work
ActionController::Base.new.expire_fragment(key, options = nil)
The solution provided by Orion works perfectly. As an enhancement and for convenience, I've put the following code into config/initializers/active_record_expire_fragment.rb
class ActiveRecord::Base
def expire_fragment(*args)
ActionController::Base.new.expire_fragment(*args)
end
end
Now, you can use expire_fragment on all instances of ActiveRecord::Base, e.g. User.first.expire_fragment('user-stats')
This is quite easy to do. You can implement Orion's suggestion, but you can also implement the broader technique illustrated below, which gives you access to the current controller from any model and for whichever purpose you decided to break MVC separation for (e.g. messing with the fragment cache, accessing current_user, generating paths/URLs, etc.)
In order to gain access to the current request's controller (if any) from any model, add the following to environment.rb or, much preferably, to a new plugin (e.g. create vendor/plugins/controller_from_model/init.rb containing the code below):
module ActiveRecord
class Base
protected
def self.thread_safe_current_controller #:nodoc:
Thread.current[:current_controller]
end
def self.thread_safe_current_controller=(controller) #:nodoc:
Thread.current[:current_controller] = controller
end
# pick up the correct current_controller version
# from ##allow_concurrency
if ##allow_concurrency
alias_method :current_controller, :thread_safe_current_controller
alias_method :current_controller=, :thread_safe_current_controller=
else
cattr_accessor :current_controller
end
end
end
Then, in app/controllers/application.rb,
class ApplicationController < ActionController::Base
before_filter { |controller|
# all models in this thread/process refer to this controller
# while processing this request
ActiveRecord::Base.current_controller = controller
}
...
Then, from any model,
if controller = ActiveRecord::Base.current_controller
# called from within a user request
else
# no controller is available, didn't get here from a request - maybe irb?
fi
Anyhow, in your particular case you might want to inject code into your various ActiveRecord::Base descendants when the relevant controller classes load, so that the actual controller-aware code still resides in app/controllers/*.rb, but it is not mandatory to do so in order to get something functional (though ugly and hard to maintain.)
Have fun!
In one of my scripts I use the following hack:
require 'action_controller/test_process'
sweepers = [ApartmentSweeper]
ActiveRecord::Base.observers = sweepers
ActiveRecord::Base.instantiate_observers
controller = ActionController::Base.new
controller.request = ActionController::TestRequest.new
controller.instance_eval do
#url = ActionController::UrlRewriter.new(request, {})
end
sweepers.each do |sweeper|
sweeper.instance.controller = controller
end
Then, once the ActiveRecord callbacks are called, sweepers are able to call expire_fragment.
I'm a bit of a rails noob, so this may not be correct, or even helpful, but it seems wrong to be trying to call controller actions from within the model.
Is it not possible to write an action within the controller that does what you want and then invoke the controller action from within your rake task?
Why not have your external rake tasks call the expiry method on the controller. Then you're still being MVC compliant, you aren't building in a dependence on some scoping hack, etc.
For that matter, why don't you just put all the daemon / external functionality on a controller and have rake / cron just call that. It would be loads easier to maintain.
-- MarkusQ
Will it not be easier and clean just to pass the current controller as an argument to the model method call? Like following:
def delete_cascade(controller)
self.categories.each do |c|
c.delete_cascade(controller)
controller.expire_fragment(%r{article_manager/list/#{c.id}.*})
end
PtSection.delete(self.id)
controller.expire_fragment(%r{category_manager/list/#{self.id}.*})
end
You can access all public methods and properties of the controller from within model.
As long as you do not modify the state of the controller, it should be fine.
This might not work for what you're doing, but you may be able to define a custom call back on your model:
class SomeModel < ActiveRecord::Base
define_callback :after_exploded
def explode
... do something that invalidates your cache ...
callback :after_exploded
end
end
You can then use a sweeper like you would normally:
class SomeModelSweeper < ActionController::Caching::Sweeper
observe SomeModel
def after_exploded(model)
... expire your cache
end
end
Let me know if this is useful!

Resources