How does this mapper pattern work? - ruby-on-rails

I found RailsCasts episode, and used this logic and code samples for my needs.
But one thing bothers me.
constraint looks like:
constraints(Subdomain.new) do ... end
which uses this code:
class Subdomain
def matches?(request)
....
end
end
end
And it works.
But I don't get two things. First, I do not invoke matches? anywhere, why this method is just executed on initializing Subdomain.new?
Second concern. I don't pass any parameter, but it somehow assigns request argument to actual rack request and it just works.
For example, I didn't like this syntax:
constraints(Subdomain.new) do ... end
so I decided to make it module with method subdomain(request), but as made it module, it started raising wrong number or arguments error (0 for 1).
I found out that method matches? is defined in mapper.rb, may be it is called somewhere backwards in rails, but this way it should be overwritten by my subdomain file? If not, as my matches is class method, how it works without any Subdomain instance to which it is applied?
As I said, everything works fine, but I would like to understand what exactly happens, because I don't like using something that appears david blane magic code to me.
Reading some source code of Rails mapper module didn't give me understanding.

Well, little more of reading source code gave me a clue. I found one more matches?, defined for #constraints
def matches?(req)
#constraints.all? do |constraint|
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
end
end
So for every constraint it checks if it responds to matches? and then invokes it with rack request argument.

Related

Monkey patching a core class with business logic with Rails

I have a monkeypatched of ActiveRecord find with some business logic, for example:
# lib/core_extensions/active_record/finder_methods/finder.rb
module ActiveRecord
module FinderMethods
def find(*args)
return super if block_given?
#... business logic code => my_error_control = true
raise "My Error" if my_error_control
retorn = find_with_ids(*args)
end
end
end
retorn
I have not seen many examples like this, and this causes me a doubt:
Where should finder.rb be?
In this example, this file is in lib/core_extensions/... but if it contains business logic, I think finder.rb should lives in the folder app/core_extensions/ isn't it?
Edited, after Sergio Answer
things like this, are a bad practice?
# lib/core_extensions/nil_class/image_attributes.rb
# suport for product images attributes
class NilClass
def main_image(size,evita_video)
"/images/paperclip_missing/original/missing.png"
end
end
Where should finder.rb be?
Ultimately, it doesn't matter. It only matters that this code gets loaded. This mix of patching base libraries and adding business logic there looks like something that MUST be documented thoroughly (in the project's wiki or something like that). And if it is documented, then it doesn't matter. The code is where the documentation says it is.
That being out of the way, here's a design suggestion:
when user seeks a Family Family.find(params[family_id],session[:company_id]), this find will compare the company of the family result family.company witht the parameter
Why not do something like this:
family = current_company.families.find(params[:family_id])
where current_company can be defined as #current_company ||= Company.find(session[:company_id])
Here, if this company doesn't have this family, you'll get an exception.
Same effect*, only without any patching. Much more futureproof. You can even add a couple of rubocop rules to ensure that you never write a naked Family.find.
* it's not like you add that patch and rest of your code magically acquires super-powers. No. You still have to change all the finders, to pass that company id.
It's the first time I see such case :). I'd put it in app/core_extensions and check if live reloading works correctly with it. If not, I'd move it to lib/. (It's just a heuristic)
Edit:
Instead of extending NilClass I'd rather use regular NullObjects. It's really less surprising and easier to understand.
https://robots.thoughtbot.com/rails-refactoring-example-introduce-null-object

Cannot cache from decorator (draper)

Caching is by far the most logic-intensive part of my view code, so I would like to do fragment caching from inside a decorator, however, I cant do it.
When i do this from my decorator:
def cached_name
h.cache do
"a name here"
end
end
I get this:
You have a nil object when you didn't expect it! You might have
expected an instance of Array. The error occurred while evaluating
nil.length
I instantiate my decorator from inside a controller
#presenter = SomePresenter::new
I am using HAML for my views
How can I succesfully cache from inside my decorator, so my view can do stuff like this
= #decorator.cached_logic_heavy_stuff
UPDATE: I have created a git repo showing my issue: https://github.com/houen/presenter_caching
UPDATE: This maybe works - see the repo
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache do
h.concat "i should still not be empty"
end
end
end
I'd suggest using Rails.cache directly might solve your problem; we do the same thing in our decorators with Rails 4.
def cached_name
Rails.cache.fetch(source) do
source.name # etc.
end
end
If you're using Draper, I believe you don't need to explicitly pass the view context. You will likely want to pass a model or collection to your draper present when you instantiate. Examples:
class UserDecorator < Draper::Base
decorates :user
# additional methods
end
# in the controller
#presenter = UserDecorator.new(#user) # for an instance
#presenter = UserDecorator.decorate(#users) # for a collection
I suspect the nil object error you're getting is coming from another method call that's not listed in your code.
As for fragment caching from your decorator, you'll want to use the concat helper method to get this to work inside the decorator:
# your decorator class
def cached_name
h.cache("some_cache_key") do
h.concat "a name here"
end
end
Rails' cache method tries to infer a cache key based on the view that it's being called from. Since you're not actually calling it from a view (but from inside an instance of a decorator class), I expect that it's bombing when trying to build a cache key.
You might try passing a cache key explicitly, via h.cache "your cache key" do. With a full stack trace, you can figure out where it's throwing the exception, and then work around that, as well. Without the full stack trace, it's harder to help you, though.
Edit: Looking at Rails' caching code, I think this might be a deeper issue; it's attempting to get the length of output_buffer, which isn't going to be available outside of your views' contexts (that is, within Draper). You might try adding:
def output_buffer
h.output_buffer
end
But without testing it, I'm thinking it might not work exactly as planned without some more work. This is just a rough guess - I'd be surprised if this is actually the issue, but hopefully it gets you on the right path.
The note in the source there:
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
indicates that this isn't a fully-solved problem, so you may need to do a little digging around in the Rails internals to make this one work.
This works:
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache "some_key10", :expires_in => 10.seconds do
h.concat "i should still not be empty 2"
end
end
end

Expect method call and proxy to original method with RSpec

I want to discover with BDD missing :include params for ActiveRecord::Base.find method. So my idea is to have in spec something like this:
ActiveRecord::Base.should_receive(:find).once.and_proxy_to_original_method
parent = SomeClass.find 34
parent.child.should be_loaded
parent.other_children.should be_loaded
If #child or #other_children associations are not eager loaded, expectation should fail with something like:
"Expected ActiveRecord::Base.find to be invoked once but it was invoked 2 more times with following args: 1. ...; 2. ..."
Does anyone know if there's some matcher that works like this or how to make this.
Thanks
I think I had the same problem here. In your particular case I would do this which I find quite clean.
original_method = ActiveRecord::Base.method(:find)
ActiveRecord::Base.should_receive(:find).once do (*args)
original_method.call(*args)
end
I believe you could extend the Rspec Mocks::MessageExpectation class to include the and_proxy_to_original_method method, shouldn't be too hard, but I haven't looked.

Changing mocha default expects exactly once

I just started using mocha and I find it annoying that when creating a new mock object, mocha expects it to be called exactly once. I have helper methods to generate my mocks and I'm doing something like this
my_mock = mock(HashOfParameters)
All of the parameters might not get called for each test method so it will raise an error:
expected exactly once, not yet invoked
So I figured I needed to do something like this:
my_mock = mock()
HashOfParameters.each do |k, v|
my_mock.expects(k).returns(v).at_least(0)
end
This works but I was wondering if there was an easier way to do this, like changing a default configuration somewhere...
Ok, that was a stupid question... I hadn't took the time to truly understand the difference between a mock and a stub. Here's a good article that shows how it works :
http://martinfowler.com/articles/mocksArentStubs.html
So in my example, I should have been using the stub method instead of mock.

Rails ActiveRecord: Automatically Alias/Append Suffix?

I've got a legacy database with a bunch of idiotically named columns like:
some_field_c
some_other_field_c
a_third_field_c
I would very much like to make a Rails ActiveRecord sub-class that automatically aliases these attributes to their name minus the underscore and "c". However, when I tried:
attributes.each_key do | key |
name = key
alias_attribute key.to_sym, key[0, (key.length -2)].to_sym if key =~ /_c$/
end
in my class definition I got an "undefined local variable or method `attributes'" error. I also tried overwriting these methods:
method_missing
respond_to?
but I kept getting errors with that route too.
So my question (actually questions) is/are:
Is what I'm trying to do even possible?
If so, what's the best way to do it (iterative aliasing or overwriting method missing)?
If it's not too much trouble, a very brief code sample of how to do #2 would be awesome.
Thanks in advance for any answers this post receives.
Your problem is probably that attributes is an instance method and you're doing this in the context of the class. The class method that's closest to what you want is column_names.
you could do something like:
methods.each do |method|
if method.ends_with("_c") then
self.send(:defind_method,method.slice(0,-2)){self.send(method)}
end
end
Hmm... I cooked up this little solution that I thought ended up pretty elegantly...
Not sure if this will work, but I aliased method_missing to still allow active_record to do it's thang:
module ShittyDatabaseMethods
alias :old_method_missing :method_missing
def method_missing(method)
if methods.include?("#{method}_c")
send("#{method}_c".to_sym)
else
old_method_missing(method)
end
end
end
class Test
attr_accessor :test_c
include ShittyDatabaseMethods
end
You may not get to name your module "ShittyDatabaseMethods", but you get the idea ;) Once you define that module and stuff it into lib, you just need to include this module and off you go :D
Would love to hear if this works out for you :)

Resources