Problems writing an activerecord alternative [duplicate] - ruby

From the book Agile Web Development With Rails
class Order < ActiveRecord::Base
named_scope :last_n_days, lambda { |days| {:conditions =>
['updated < ?' , days] } }
named_scope :checks, :conditions => {:pay_type => :check}
end
The statement
orders = Orders.checks.last_n_days(7)
will result to only one query to the database.
How does rails implement this? I'm new to Ruby and I'm wondering if there's a special construct that allows this to happen.
To be able to chain methods like that, the functions generated by named_scope must be returning themselves or an object than can be scoped further. But how does Ruby know that it is the last function call and that it should query the database now?
I ask this because the statement above actually queries the database and not just returns an SQL statement that results from chaining.

There are two tricks (or patterns if you will) employed in the named_scope magic.
Proxy pattern - calling a named scope method on a class or an association always returns an instance of the ActiveRecord::NamedScope::Scope class, not a colleciton of filtered AR objects. This pattern, altough very useful, makes things kind of blurry sometimes, since the proxy objects are ambivalent in their nature.
Lazy loading - thanks to lazy loading (which in this context means - hitting the database only if neccessary) named scopes can be chained up to the point when you need to work with the collection defined by the scopes. Whenever you request the underlying colleciton, all the chained scopes are evaluated and a database query is executed.
One final note: There's one thing to have in mind when playing with named scopes (or with any thing that uses delegation of some kind) in IRB. Everytime you hit Enter, the thing you wrote beforehand is evaluated and the inspect method is called on the returned value. In the case of chained named scopes, although the whole expression is evaluated to a Scope instance, when the IRB calls the inspect method on it, the scopes are evaluated and the database query is fired. This is caused by the fact that the inspect method is by means of delegation propagated through all the scope objects up to the underlying collection.

You might want to try this
class Order < ActiveRecord::Base
class << self
def last_n_days(n)
scoped(:conditions => ['updated < ?', days])
end
def checks
scoped(:conditions => {:pay_type => :check})
end
end
end
usage is the same
#orders = Order.last_n_days(5)
#orders = Order.checks
#orders = Order.checks.last_n_days(5)
This still does all the lazy loading you love. That is, it won't make a query until you attempt to access the records. Bonus: Rails 3 compatible!
Named Scopes Are Dead

Very cool. I was thinking of doing something like this in Javascript but Javascript behaves rather weird.
The statement:
var x = SomeObject;
does not call SomeObject's toString() function. But the statement:
var x;
x = SomeObject;
correctly calls the toString() function as expected.
This prevents Javascript from doing cool stuff with chaining. =(

Related

How can I mock something that "does not implement" a particular method?

The Background:
I'm trying to use cucumber to do some test-driven (or behavior-driven) development around an interface to AWS, in ruby.
So, I have a step definition that looks like this:
Then(/^the mock object should have had :(.*?) called, setting "(.*?)" to "(.*?)"$/) do |method, param, value|
expect(#mock).to receive(method.to_sym).with(hash_including(param, value))
end
Where #mock was previously set using:
#mock = instance_double(AWS::AutoScaling::Client)
And where I invoke this step definition with a feature line like:
And the mock object should have had :update_auto_scaling_group called, setting "auto_scaling_group_name" to "Some-test-value"
When that step gets run, it gets the following error (leaving out the full error, as I believe this is the most relevant part):
AWS::AutoScaling::Client does not implement: update_auto_scaling_group (RSpec::Mocks::MockExpectationError)
I see that indeed, the checks that RSpec runs (as traced back from where the RSpec::Mocks::MockExpectationError gets thrown) are at least correctly reporting the information that they get from the class:
[1] pry(main)> require 'aws-sdk'
=> true
[2] pry(main)> klass = AWS::AutoScaling::Client
=> AWS::AutoScaling::Client
[3] pry(main)> klass.public_method_defined? "update_auto_scaling_group"
=> false
[4] pry(main)> klass.private_method_defined? "update_auto_scaling_group"
=> false
[5] pry(main)> klass.protected_method_defined? "update_auto_scaling_group"
=> false
And yet, if we ask an actual instance, it lets us know that this is a method it would respond to:
[6] pry(main)> x = klass.new
=> #<AWS::AutoScaling::Client::V20110101>
[7] pry(main)> x.respond_to? "update_auto_scaling_group"
=> true
Even while it doesn't say that about just anything:
[8] pry(main)> x.respond_to? "bogus"
=> false
First questions:
So... is this a bug in the AWS::AutoScaling::Client code (or really, probably here), for not defining the methods in a way that the extant checks ({public,private,protected}_method_defined?) would come back true?
Or perhaps a bug in RSpec's "doubles", for not doing all the checking it could do to try to find out that this is indeed a method that's callable in an instance of that class?
Or perhaps it's simply something that I'm doing wrong here? Other?
More generally:
How can I write tests for the code I'm writing, to ensure that it's making calls to what will be an AWS::AutoScaling::Client instance, with the correct parameters (as defined in several checks that I have)? Are there alternate ways I can write my step definitions that would make this work? Alternative ways to create my mock objects? Other?
I've found a way to dynamically mix in the methods I needed to mock
You could do this with empty methods and then stub them, or just include the stubs in the mixin
require 'rails_helper'
RSpec.describe "users/sessions/new.html.erb", :type => :view do
it "displays login form" do
module DeviseUserBits
def resource
#_DeviseUserBitsUser ||= User.new
end
def resource_name
:user
end
def devise_mapping
Devise.mappings[:user]
end
end
view.class.include DeviseUserBits
render
expect(rendered).to match /form/
end
end
It just adds methods on/after instantiating. It's pretty legal, all ruby classes/objects are open.
Proper answer - you do not want to test what you are trying to test in duck-typed language with open classes and objects. It just does not make sense.
The version 1 AWS SDK for Ruby uses #method_missing as a delegate for building and sending requests. The methods a client responds to are defined in an API definition. This eliminates boiler-plate code, but causes problems if you are trying to reflect the available methods at runtime.
Option A: Use a regular double and apply your assertions on the test double.
Option B: Use the mocking feature of the SDK via AWS.stub! When stubbing is enabled, all clients constructed will respond to their regular methods, but will return dummy responses (empty hashes and arrays). This approach provides the useful ability to specify the data to return from a stub. You can even create a stub response for the express purpose of returning from an assertion.
Going with Option B:
# use `:stub_requests` or call Aws.stub!
as = AWS::AutoScaling::Client.new(:stub_requests: true)
# validates parameters as normal, but returns empty response data
as.update_auto_scaling_group(auto_scaling_group_name: 'name')
#=> {}
# You can access the stub response for any operation by name:
stub = as.stub_for(:describe_auto_scaling_groups)
stub.data[:auto_scaling_group_names] = ["Group1", "Group2"]
# Now calling that operation will return the stubbed data
resp = as.describe_auto_scaling_groups
resp.auto_scaling_group_names
#=> ['Group1', 'Group2']
If you need to assert a method is called against the client, you can do so normally, returning the stubbed response:
expect(#client).to receive(:describe_auto_scaling_groups).
with(hash_including(param, value)).
and_return(#client.stub_for(:describe_auto_scaling_groups))

Sharing a class instance between two classes

I have two different classes that both represent objects that need to be persisted to my database and now I want to share the database client object between the two classes. I want to avoid instantiating the client object more than once.
Currently I do this by using a global variable
$client = Mysql2::Client.new(:database => "myDb", :user => "user", :password => "password", :host => "localhost")
class Person
def save
$client.query("INSERT INTO persons")
end
end
class Car
def save
$client.query("INSERT INTO cars")
end
end
This works, but I am wondering if there are more correct ways to do this and why they are more correct?
You can inherit from a parent class. This allows you to share common functionality across objects and follows DRY (do not repeat yourself) programming principles. It will also allow you to protect your DB connection with locks, resuces, queues, pools, and whatever else you may want to do without having to worry about it in your children classes
class Record
#table_name = nil
##client = Mysql2::Client.new(:database => "myDb", :user => "user", :password => "password", :host => "localhost")
def save
##client.query("INSERT INTO #{#table_name}") if #table_name
end
end
class Person < Record
#table_name = "persons"
end
class Car < Record
#table_name = "cars"
end
While we are on the subject, you should look at using ActiveRecord for handling your database models and connections. It already does pretty much anything you'll need and will be more compatible with other gems already out there. It can be used without rails.
As an alternative on using inheritance, why not consider a simple Singleton pattern? This could make your models cleaner, by separating the responsibility outside your classes. And eliminating the need for inheritance.
The example below illustrates this. Only one, single instance of the DataManager class can exist. So, you'll only instantiate it once - but can use it everywhere:
require 'singleton'
class DataManager
include Singleton
attr_accessor :last_run_query
def initialize()
if #client.nil?
p "Initialize the Mysql client here - note that this'll only be called once..."
end
end
def query(args)
# do your magic here
#last_run_query = args
end
end
Next, calling it using the .instance accessor is a breeze - and will always point to one single instance, like so:
# Fetch, or create a new singleton instance
first = DataManager.instance
first.query('drop table mother')
p first.last_run_query
# Again, fetch or create a new instance
# this'll actually just fetch the first instance from above
second = DataManager.instance
p second.last_run_query
# last line prints: "drop table mother"
For the record, the Singleton pattern can have some downsides and using it frequently results in a never-ending debate on whether you should use it or not. But in my opinion it's a decent alternative to your specific question.

How to set a method dynamically as other class method

Im new to Ruby, and im creating a cli app with Thor and some additional gems. My problem is that i take user input (from the console) and pass the data as a variable to a existing method (This method is from a gem)
My method
def search(searchtype, searchterm)
search = OtherClass.new
result = search.search.searchtype keyword: "#{searchterm}"
puts result
# search.search.searchtype is not a method in the gem im using.
end
The OtherClass gem has these search methods: users, repos
The users method
def users(*args)
arguments(args, :required => [:keyword])
get_request("/legacy/user/search/#{escape_uri(keyword)}", arguments.params)
end
The repos method
def repos(*args)
arguments(args, :required => [:keyword])
get_request("/legacy/repos/search/#{escape_uri(keyword)}", arguments.params)
end
So how can i pass in the user data to the method from the OtherClass? Heres something like what i would want to do. The SEARCHTERM would be dynamically passed to the search.search object as a method parameter.
def search(SEARCHTYPE, searchterm)
search = OtherClass.new
result = search.search.SEARCHTYPE keyword: "#{searchterm}"
puts result
end
The "#{searchterm}" works as expected, but i also want to pass in the method to the search.search object dynamically, this could probably be done with if's but im sure theres a better way, maybe the Ruby way to solve this problem.
Finally i would want to be able to use this little program like this (the serch method)
./search.rb search opensource linux
(where opensource could be users, or another type of search, and linux could be the search keyword for the searchtype)
If this is possible i would apprechiate any help!
Thnx!
If you'd like to call a method dynamically, use Object#send.
http://ruby-doc.org/core-1.9.3/Object.html#method-i-send
I would caution against sending a method that was obtained by user input though, for security reasons.

Rails 3, confused about 'before_create :some_method' ... WHEN does some_method do its thing?

we have model helper (used by several different models) called set_guids that sets self.theguid to a random string. Been using it for a long time, we know it works.
in a new model 'Dish' we created, we have
before_create :set_guids (NOTE: no other before/after/validation, just this)
def do_meat_dish
( this is invoked by #somemeat.do_meat_dish in the Dish contoller )
( it manipulated the #somemeat object using self.this and self.that, works fine)
( THEN sometimes it creates a new object of SAME MODEL type )
( which is handled differently)
#veggie = Dish.new
#veggie.do_veggie_dish
end
def do_veggie_dish
recipe_str = "add the XXXX to water"
recipe_str.gsub!("XXXX", self.theguid) *** the PROBLEM: self.theguid is nil
end
as soon as we execute veggie = Dish.new shouldn't veggie.theguid be initialized?
Note we have not saved the new object yet... but the before_create should still have done its thing, right?
it is something to do with create a new instance of a model inside a method for the same model?
is it something with using # for the variables?
Additional note: if we comment out the line trying to access self.theguid everything else works fine ... it's ONLY the value (supposedly) set by the before_create set_guids that is nil instead of being a guid.
before_create is called only before the object is saved to the database the first time. That's why you get nil.
I suggest that you use after_initialize callback instead. Be careful though, since after_initialize will be called whenever the document is new or loaded from the db, that way you will have new guids every time you get the document, which is not what you want. So I suggest you do something like:
def set_guids
return unless theguid.nil?
.....
end
As another solution, if you don't want to change the after_create callback above, you can do something like:
def theguid
super || set_guids
end
That should let you go also.

Alternative initialize for a Class to avoid processing already known information

I have a class, Autodrop, that contains several methods , a.o. 'metadata', that call an external API (dropbox). They are slow.
However, I already often have that metadata around when initializing the AutodropImage, so I should make the methods smarter.
What I have in mind is this:
class Autodrop
include Dropbox
attr_reader :path
def initialize(path)
#path = path
end
def self.from_entry(drop_entry)
#drop_entry = drop_entry
self.initialize(#drop_entry.path)
end
def metadata
if #drop_entry = nil
return heavy_lifting_and_network_traffic
else
return #drop_entry.metadata
end
end
#...
end
Now, I would expect to call
entry = BarEntry.new()
foo = Autodrop.from_entry(entry)
foo.metadata
In order to avoid that heavy lifting and network traffic call.
But this does not work. And somehow, in all my newbieness, I am sure I am goind at this all wrong.
Is there a term I should look for and read about first? How would you go for this?
Note, that the examples are simplified: in my code, I inherit AutodropImage < Autodrop for example, which is called from withing AutodropGallery < Autodrop. The latter already knows all metadata for the AutodropImage, so I mostly want to avoid AutodropImage going over the heavy lifting again.
You are creating an instance variable #drop_entry in your class method from_entry and obviously it wont be available to your object that you are creating in this method. One workaround is to pass it as a parameter when you are initializing the class. It should work if you do the following modifications:
In your from_entry class method change
self.initialize(#drop_entry)
to
new(#drop_entry)
Modify initialize method to:
def initialize(drop_entry)
#drop_entry = drop_entry
#path = #drop_entry.path
end
Or if your class is tied up to pass only the path parameter, ie. you dont want to change the other existing code then you can use an optional parameter drop entry like so
def initialize(path, drop_entry=nil)
You would need to cache the metadata in a class variable.
Edit: Or in a class level instance variable.
Maybe this read will help: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

Resources