Injecting a scope through an association extension in Rails 2.3.17 - ruby-on-rails-2

I'm upgrading from Rails 2.3.5 to 2.3.17 and have come across a pretty obscure problem. I use the following extensions to inject a scope within an association accessor, and provider a custom builder:
module XYZFilterExtension
def in_context(context)
if proxy_owner.context == :abc && context != :admin
scoped(:conditions => {:startup => false})
else
scoped({})
end
end
end
module OtherExtension
def build_with_component_instance(attributes = nil)
attributes ||= {}
attributes.reverse_merge!(:parent_id => proxy_owner.component_instance.id)
instance = build
instance.build_component_instance
instance.attributes = attributes
instance
end
end
has_many :pages, :extend => [ABCExtension, OtherExtension]
In Rails 2.3.5 I could call:
Something.pages.in_context(:abc).build_with_component_instance
and I'd get a Page object (And it's associated component_instance, the build is complicated because it's a polymorphic association being built from the other direction).
Now I get:
undefined method `build_with_component_instance' for #<Class:0x1151dcae8>
Inspecting the scope, the only difference that I could find is that calling proxy_scope on the scope created by in_context() returns the Page model in 2.3.17 and the scope in 2.3.5.
I'm not sure where to go from here. I can't extract the scope out into a module to include in each model because I need to make a decision based on the proxy_owner in the association.
Update: It appears the problem is around extension methods not being available within the context of a scope. Pretty strange but I guess it kind of makes sense. Unfortunately both my scope definition and the build extension require knowledge of their association context. Any ideas welcome :)

I ended up not finding a way to work around this. In the end I had to avoid the bug in the specific situation in which it occurred. Fortunately it was only in a couple of places in the app.

Related

Improving on hacky ruby 'method_alias' fix for conflicting redmine plugins?

Using redmine 3.x, I have a dependency conflict between two plugins - redmine subtask list accordion and Subtask list inherited fields. Installing both raises 500 errors when trying to view an issue.
ActionView::Template::Error (undefined method `sla_has_grandson_issues?' for #<#<Class:0x000056319e27d668>:0x00007f237ad02588>):
1: <% if sla_has_grandson_issues?(#issue) %>
2: <%= content_for :header_tags do
3: stylesheet_link_tag(sla_use_css, :plugin => "redmine_subtask_list_accordion") +
4: javascript_include_tag("subtask_list_accordion" + (subtask_tree_client_processing? ? "_client" : ""), :plugin => "redmine_subtask_list_accordion")
plugins/redmine_subtask_list_accordion/app/views/issues/_subtask_list_accordion_partial.html.erb:1:in `_292e8187f64bee60c61b7b15c99630ab'
After lots of trial and error we fixed the issue by adding the following to the original source code of the first plugin:
included do
alias_method :render_descendants_tree_original, :render_descendants_tree
alias_method :render_descendants_tree, :switch_render_descendants_tree
alias_method :sla_use_css, :sla_use_css
alias_method :switch_render_descendants_tree, :switch_render_descendants_tree
alias_method :render_descendants_tree_accordion, :render_descendants_tree_accordion
alias_method :expand_tree_at_first?, :expand_tree_at_first?
alias_method :sla_has_grandson_issues?, :sla_has_grandson_issues?
alias_method :subtask_tree_client_processing?, :subtask_tree_client_processing?
alias_method :subtask_list_accordion_tree_render_32?, :subtask_list_accordion_tree_render_32?
alias_method :subtask_list_accordion_tree_render_33?, :subtask_list_accordion_tree_render_33?
alias_method :subtask_list_accordion_tree_render_34?, :subtask_list_accordion_tree_render_34?
This is the original code:
https://github.com/GEROMAX/redmine_subtask_list_accordion/blob/master/lib/redmine_subtask_list_accordion/patches/issues_helper_patch.rb
Which has the first two alias_method calls from the above source code.
By making an alias method with the same name for each method in the original class, the code works fine. However this seems like a hacky fix, and I don't understand why it works. Can someone explain why the fix works and how to rewrite it properly?
alias_method creates a copy of the named method in the context where it is called.
Now, if the original method (second argument to alias_method) is defined somewhere up the inheritance chain and changed in any way later on, you still have that copy around that did not change. Which might explain the behavior you see.
As for the rewriting: As a rule of thumb, throw out all method aliasing (in both plugins) and use Module#prepend. This SO Answer is a nice overview of good and bad techniques for (monkey) patching Ruby code.
Patching Rails' view helpers as appears to be the case in your plugins can be tricky due to the way Rails handles them (and results may vary depending on code loading order). Wherever possible, avoid it or create new helper modules and add them to the relevant controllers using something like
module RedmineSubtaskListAccordion
module IssuesHelper
def render_descendants_tree(issue)
if sla_has_grandson_issues?(issue) && !subtask_tree_client_processing?
render_descendants_tree_accordion(issue)
else
super # this will call the stock Redmine IssuesHelper#render_descendants_tree method
end
end
end
end
# in init.rb:
IssuesController.class_eval do
helper RedmineSubtaskListAccordion::IssuesHelper
end
I happened to write a blog post about this exact thing a while ago. Hope it's OK to refer to that here.
Obviously there may still be conflicts if two plugins expect a certain Redmine method they are changing to behave in the way it does in stock Redmine, but removing method aliasing and not trying to monkey patch already existing helpers in my experience helps to avoid many problems.
Update: Most of the other methods in that particular module you linked to actually do not belong there, but could for example be mixed into the Issue model (like sla_has_grandson_issues? or declared as methods in the top level plugin namespace (all the settings and Redmine version related stuff - eg RedmineSubtaskListAccordion.tree_render_34?).

Caching ActiveRecord model instance methods

Say I have a user model. It has an instance method called status. Status is not an association. It doesn't follow any active record pattern because it's a database already in production.
class User < ActiveRecord::Base
def status
Connection.where(machine_user_id: self.id).last
end
end
So I do this.
#users = User.all
First of all I can't eager load the status method.
#users.includes(:status).load
Second of all I can't cache that method within the array of users.
Rails.cache.write("user", #users)
The status method never gets called until the view layer it seems like.
What is the recommended way of caching this method.
Maybe this instance method is not what I want to do. I've looked at scope but it doesn't look like what I want to do.
Maybe I just need an association? Then I get the includes and I can cache.
But can associations handle complex logic. In this case the instance method is a simple query. What if I have complex logic in that instance method?
Thanks for any help.
Have You tried to encapsulate this logic inside some plain Ruby object like this (I wouldn't use this for very large sets though):
class UserStatuses
def self.users_and_statuses
Rails.cache.fetch "users_statuses", :expires_in => 30.minutes do
User.all.inject({}) {|hsh, u| hsh[u.id] = u.status; hsh }
end
end
end
After that You can use some helper method to access cached version
class User < ActiverRecord::Base
def cached_status
UserStatuses.users_and_statuses[id]
end
end
It doesn't solve Your eager loading problem, Rails doesn't have any cache warming up techniques built in. But by extracting like this, it's easily done by running rake task in Cron.
Also in this case I don't see any problems with using association. Rails associations allows You to submit different options including foreign and primary keys.

Determine given module is rails engine

Hi I have two modules
Admin
Blog (Blog is a rails engine)
where Admin is a module for namespacing admin features of app, but Blog is a module representing rails engine. Is there a better way to determine which among them is engine, like a function "is_engine?"
Admin.is_engine?
=> false
Blog.is_engine?
=> true
Definately I can have a try catch thing to determine this
def is_engine? module
module::Engine
true
rescue NameError
false
end
here
is_engine? Admin
will return false
is_engine? Blog
will return true
Thanks
I'm not sure I understand what you are trying to do: a Rails Engine is a class (a subclass of Rails::Engine), not a module.
If you have an instance, you could use:
admin.kind_of?(Rails::Engine)
If you have a class, you can use:
Something.ancestors.include?(Rails::Engine)
If what you have is a module, then it cannot be a subclass of Rails::Engine, and it's not an engine.
EDIT
If you have a module or constant something and want to know if there's a constant with a certain name in its namespace, you can use:
something.constants.include?(:Engine)

Overriding AR order scope

I have a Project model and a Developer model. I have the concept of calculating the "interestingness" for a project for a particular developer:
class Project < ActiveRecord::Base
def interestingness_for(developer)
some_integer_based_on_some_calculations
end
end
I think it would be neat, instead of having something like Project.order_by_interestingness_for(bill), to be able to say
Project.order(:interestingness, :developer => bill)
and have it be a scope as opposed to just a function, so I can do stuff like
Project.order(:interestingness, :developer => bill).limit(10)
I don't know how to do this, though, because it's not obvious to me how to override a scope. Any advice?
Assuming you will not need to use the standard ActiveRecord order query method for the Project class, you can override it like any other class method:
def self.order(type, options)
self.send(:"special_#{type}_calculation_via_scopes", options)
end
Then the trick is to ensure you create the needed calculation methods (which will vary according to your interestingness and other algorithms). And that the calculation methods only use scopes or other AR query interface methods. If you aren't comfortable converting the method logic to a SQL equivalent using the query interface, you can try using the Squeel DSL gem which can potentially work with the method directly depending on your specific calculation.
If you may be needing the classic order method (and this is usually a safe assumption), then don't override it. Either create a proxy non-ActiveRecord object for this purpose, or use a different naming convention.
If you really want to, you can use aliasing to achieve a similar effect, but it may have unintended consequences for the long term if the second argument ('options' in this case) suddenly takes on another meaning as Rails progresses. Here is an example of what you can use:
def self.order_with_options(type, options = nil)
if options.nil?
order_without_options(type)
else
self.send(:"special_#{type}_calculation_via_scopes", options)
end
end
class << self
alias_method_chain :order, :options
end

Single Table Inheritance and routing in Ruby on Rails 3.0

I am having some trouble getting routing to play nicely with Single Table Inheritance in my Ruby on Rails application. I am using Ruby 1.9.2 and Rails 3.0.6. This is in development so the back-end is SQLite3, in case that makes a difference.
Let's say I have two products, widgets and sprockets. My application keeps track of bug numbers and support case tickets for both the products but the bugs and support tickets themselves are stored in other systems. There are two separate teams that work on these two products.
I have implemented Single Table Inheritance for the two types of bug records because the validation rules for widget bug numbers and sprockets bug numbers are different (the two teams use different bug tracking systems) and there is a possibility that I will have to add further products to the application that behave wildly differently. Using STI gives me the flexibility to implement additional methods and properties as required.
The widgets team only cares about widgets information and the sprockets team only cares about sprockets information. There is a third team that needs to be able to view the information on both the widgets and the sprockets. The widgets team will access the application using the path /widgets and the sprockets team will access the application using the path /sprockets. I set this up in routes.rb using namespaces:
resources :bugs
namespace "widgets" do
resources :bugs
end
namespace "sprockets" do
resources :bugs
end
I have set up the following models which work as expected when I fire up irb and use WidgetBug.create() or SprocketBug.create():
bug.rb
class Bug < ActiveRecord::Base
end
widget_bug.rb
class WidgetBug < Bug
# Some validation rules
end
sprocket_bug.rb
class SprocketBug < Bug
# Some different validation rules
end
I used scaffolding to create the controller and the view for the bug object, then modified the controller to try to generalize it so it could be used with both widgets bugs and sprockets bugs. For example, the index method looks like this:
def index
# The scaffold code lists all bugs, which is not what we want
# #bugs = Bug.all
# Only return bugs of the subclass we're looking for
#bugs = eval("#{params[:controller].classify}.all")
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #bugs }
end
end
I then used create() to populate the database with several bugs of each type. Unfortunately when I browse to /widgets/bugs, the bugs for both products appear. After some debugging, I determined that the classify call is returning Widgets::Bugs or Sprockets::Bugs, so when I call all on it, it appears to be running against the superclass instead of running against the subclass.
I've reviewed the routing documentation and done quite a bit of searching on Google but I'm still at a loss as to how I can change the routing or the controller to make this work correctly. Any help would be greatly appreciated.
Checkout this post: STI, one controller
Routes
resources :widgets, :controller => "bugs", :type => "Widget"
resources :sprockets, :controller => "bugs", :type => "Sprocket"
# And I don't know if you need this
resources :bugs, :type => "Bug"
Controller
def index
#bugs = params[:type].constantize.all
end
UPD
namespace "widgets" do
resources :bugs, :type => "Widget"
end
namespace "sprockets" do
resources :bugs, :type => "Sprocket"
end
I wrote a blog post on STI in Rails 3 that discusses some common pitfalls and proper work arounds, including the problem you mention. http://www.christopherbloom.com/2012/02/01/notes-on-sti-in-rails-3-0/

Resources