Thank you for the insights...
If it is not to advanced it would be nice if you show us how to communicate with apis that don't follow the best-practise of REST.
First example that comes into my mind is flickr. The first stumbling block is that the url you need to generate doesn't include the ".xml", or the resource (nor the id where appropriate). Basically what you need is a fixed url with everything! else in the querystring...
I guess you would have to overwrite the url generation for ActiveResource but i don't even know where to start. Beside i guess it is just the beginning of the problems as i guess the next step would be to parse the response xml by hand... Do you intend to go into these kinds of problems or do you consider this as to advanced/specialized problems?
(BTW,Put the champagne into the fridge you deserve to treat yourself and be proud for the work you've done so far for the community.. we are looking forward for an other 1000;-) screencasts once you finalized with the first 100. THANK YOU so far)
The best way I can think of doing that is having a method that simply redefines the site variable for every username/password. For example in your active resource model, you could have a method:
def assign_to_user(username, password)
self.class.site = "http://#{username}:#{password}..."
end
and something similar as a class method too. As of now, when site is assigned, it immediately sends it to the URI class to parse which unfortunately means having something like a proc that gets called at the time of the actual request would require quite a few changes.
I hope I was helpful. :)
Thanks so much Ryan Bates! Your screen casts are very helpful, I particularly liked the refactoring screen cast.
@Ryan, Arya's suggestion should work. Alternatively you can try overriding the "site" class method:
def self.site
"http://#{name}:#{pass}@example.com"
end
I don't know if that is supported, so you'll want to thoroughly test it.
@nicolash, great question! From my understanding ActiveResource isn't really designed to work with other APIs which don't follow the same REST interface. I believe it will take too much customization to the point you lose most of the benefits of ActiveResource. You're probably better off using a plugin designed specifically for that API or writing one yourself. Someone please correct me if I'm wrong.
@andy, good question. I usually don't use nested resources, but I know many do so I looked into it while planning this episode. Unfortunately I wasn't able to find a solution. There are some examples where you set it in the "site" string as shown here:
http://whynotwiki.com/ActiveResource
But I was not able to get this to work. If anyone knows the solution please reply.
First of all, happy birthday, I've been watching since day one (actually day 14, since I heard of you in RubyInside which posted the announcement on March 19th :)) and even after two years of using rails I learn a few little things here and there every week -- particularly those handy textmate shortcuts! :D
THANK YOU!
Second, and I'm just nitpicking, so don't mind me ;), the discontinue action should conceptually be a DELETE, not a PUT. By discontinuing the product you're "removing" it from the cloud, even if you aren't deleting it from the database :)
@Ryan Townsend:
In edge rails you don't have to override self.site anymore, you can use self.user and self.password :)
@andy
I think it's only possible to access one nest of resources in one model, i think it could get complicated when you're changing self.site at runtime. Anyway, for myself, I was able to run ActiveResources through nested resources, all I've done was:
class Project < ActiveResource::Base
self.site = http://localhost:3000/users/:user_id
end
If you like to call a find method you have to do it like this:
Project.find(:all, :params => {:user_id => 5})
For sure your REST Service has to support this kind of request.
Great screencast again! I really like the way you were using script/console that you can see the exact http request on the server side. Can you kindly tell me how to setup it?
Thanks so much for all these screencasts. I've pretty much downloaded them all now, and refer to them a LOT whenever I forget things. This is an excellent resource!
Ryan, just wondering how you call a method on the model on the remote site? For example, my host has a model called Post. The Post class has some methods called find_all_by_permalink(permalink) and find_all_by_date(year, month, day). If I try to call these methods from the Weblog controller on the client side, I get an error saying the method is undefined. How do I expose this method to my client? Or do I *have* to define a new action on the host side which does the call to Post.find_by_date for me? I've not been able to find anything on this anywhere. Thanks for your awesome casts - I hang out for them each week.
i love your railscasts. i'm sure it helps pushing rails a lot.
my question:
is there a way to get ActiveResource objects when using custom methods like get instead of hash/array of hashes?
I second Chris's question. It seems like if you do MyActiveResource.find(1).get(:custom_method), ActiveResource returns a hash instead of object that you can call with methods.
Update: in the ActiveResource code, all the find methods are calling the private method instantiate_record, which essentially just does a Base.new(hash_returned_from_get). There's also a similar private method instantiate_collection.
Unfortunately, these are private methods - it would be great if they were protected, so we could call them from our subclasses. In the meantime, I just use the dirty dirty workaround:
def get_record(path)
MyActiveResource.new(get(path))
end
I got around the authentication by doing the following:
class Pie < ActiveResource::Base
self.site = "https://api.pie.com/"
def self.for_user(user)
self.site = "https://#{user}@api.pie.com/"
return self
end
end
rs = Pie.for_user(1).find(:all)
puts rs
One problem though is that you have to make sure after you use it to reset self.site to the correct url, otherwise there could be a security breach and you could access someone elses stuff elsewhere in your code. Something like this should really get built into ActiveResource
Am I off my rocker or is any solution that modifies self.site going to be un-thread-safe?
If you have 3 processes accessing 3 different sites (different authentications) then when you modify self.site for any one of them, you're either going to have to lock out other processes until you can "reset" it or you're going to allow each unique process to use a random authentication willy nilly.
Ok, this post is a little old but for those looking for a nice solution to the ActiveResource user login question you can now just use the following syntax. Clean and simple. Just like Rails.
class Pie < ActiveResource::Base
self.site = "http://api.pie.com/"
self.user = username
self.password = password
end
I am having exact problem
when debugged i found out that this might be because when we try to save using active resource xml request
in params: _method=> "put" was not there.
not able to solve this yet..
will appreciate any help
If you encounter "EOFError (end of file reached)" on the consumer, and <Mongrel::HttpParserError: Invalid HTTP format, parsing fails.> on the REST provider, you might need a leading slash on the ActiveResource prefix. In this RailsCast, it shows
I've played with ActiveResource a bit recently, and found it lacking. I did, however, write up a little bit about it here on my blog; mostly dealing with hacks (or, as some might say, work-arounds) to deal with shallow nested routes.
I was having the same problem as comments #19 and 20 with custom query methods returning hashes rather than objects. I didn't find what I was looking for in the online docs, but looking at activeresource-2.3.5/lib/active_resource/base.rb line 544 I found a :from option to find. Using the syntax from the provided examples, I can get back a collection of objects from a custom query. The custom query just needs to send back a naked array of valid JSON objects. For example:
Is it possible to forward a cookie from the user browsing my site, trough ActiveResource? So that the site in self.site receives the cookie from my user?
Was waiting for this screencast :) I had no more railscast to see and I am almost finishing the design patterns on ruby book :)
ActiveResource seems like a very powerful backend to data models and I think we got a keeper here.
Other frameworks will copy this in no time :)
If your storing the user and pass in the site within the model, how would you dynamically change that based upon what user is logged into to the app?
So for example being able to do:
self.site = 'http://' + un + ':' + pw + '@localhost:3000/'
Thank you for the insights...
If it is not to advanced it would be nice if you show us how to communicate with apis that don't follow the best-practise of REST.
First example that comes into my mind is flickr. The first stumbling block is that the url you need to generate doesn't include the ".xml", or the resource (nor the id where appropriate). Basically what you need is a fixed url with everything! else in the querystring...
I guess you would have to overwrite the url generation for ActiveResource but i don't even know where to start. Beside i guess it is just the beginning of the problems as i guess the next step would be to parse the response xml by hand... Do you intend to go into these kinds of problems or do you consider this as to advanced/specialized problems?
(BTW,Put the champagne into the fridge you deserve to treat yourself and be proud for the work you've done so far for the community.. we are looking forward for an other 1000;-) screencasts once you finalized with the first 100. THANK YOU so far)
@Ryan Townsend
The best way I can think of doing that is having a method that simply redefines the site variable for every username/password. For example in your active resource model, you could have a method:
def assign_to_user(username, password)
self.class.site = "http://#{username}:#{password}..."
end
and something similar as a class method too. As of now, when site is assigned, it immediately sends it to the URI class to parse which unfortunately means having something like a proc that gets called at the time of the actual request would require quite a few changes.
I hope I was helpful. :)
Thanks so much Ryan Bates! Your screen casts are very helpful, I particularly liked the refactoring screen cast.
Congratz on the year anniversary! Your screencasts trump every Ruby/Rails book I've yet read... and for FREE no less! Thanks!
@Ryan, Arya's suggestion should work. Alternatively you can try overriding the "site" class method:
def self.site
"http://#{name}:#{pass}@example.com"
end
I don't know if that is supported, so you'll want to thoroughly test it.
@nicolash, great question! From my understanding ActiveResource isn't really designed to work with other APIs which don't follow the same REST interface. I believe it will take too much customization to the point you lose most of the benefits of ActiveResource. You're probably better off using a plugin designed specifically for that API or writing one yourself. Someone please correct me if I'm wrong.
Ryan - how would you use ARes to access a nested resource, like:
/admin/users #namespace route
/users/posts #nested route
Thanks for these screencasts!!!
@andy, good question. I usually don't use nested resources, but I know many do so I looked into it while planning this episode. Unfortunately I wasn't able to find a solution. There are some examples where you set it in the "site" string as shown here:
http://whynotwiki.com/ActiveResource
But I was not able to get this to work. If anyone knows the solution please reply.
Great screencasts - could you please do one on making drag and drop widgets?
First of all, happy birthday, I've been watching since day one (actually day 14, since I heard of you in RubyInside which posted the announcement on March 19th :)) and even after two years of using rails I learn a few little things here and there every week -- particularly those handy textmate shortcuts! :D
THANK YOU!
Second, and I'm just nitpicking, so don't mind me ;), the discontinue action should conceptually be a DELETE, not a PUT. By discontinuing the product you're "removing" it from the cloud, even if you aren't deleting it from the database :)
@Ryan Townsend:
In edge rails you don't have to override self.site anymore, you can use self.user and self.password :)
http://blog.codefront.net/2008/02/27/living-on-the-edge-of-rails-9-the-sleeper-edition/
@andy
I think it's only possible to access one nest of resources in one model, i think it could get complicated when you're changing self.site at runtime. Anyway, for myself, I was able to run ActiveResources through nested resources, all I've done was:
class Project < ActiveResource::Base
self.site = http://localhost:3000/users/:user_id
end
If you like to call a find method you have to do it like this:
Project.find(:all, :params => {:user_id => 5})
For sure your REST Service has to support this kind of request.
Great screencast again! I really like the way you were using script/console that you can see the exact http request on the server side. Can you kindly tell me how to setup it?
Thanks again.
Thanks so much for all these screencasts. I've pretty much downloaded them all now, and refer to them a LOT whenever I forget things. This is an excellent resource!
Ryan, just wondering how you call a method on the model on the remote site? For example, my host has a model called Post. The Post class has some methods called find_all_by_permalink(permalink) and find_all_by_date(year, month, day). If I try to call these methods from the Weblog controller on the client side, I get an error saying the method is undefined. How do I expose this method to my client? Or do I *have* to define a new action on the host side which does the call to Post.find_by_date for me? I've not been able to find anything on this anywhere. Thanks for your awesome casts - I hang out for them each week.
Hey Ryan - love the railscasts - thank you so much- your pod-cast is like a reference guide to me!
+1 votes for drag and drop cast!
Is it possible to block such as the one showing fields?
hi ryan,
i love your railscasts. i'm sure it helps pushing rails a lot.
my question:
is there a way to get ActiveResource objects when using custom methods like get instead of hash/array of hashes?
big up
chris
I second Chris's question. It seems like if you do MyActiveResource.find(1).get(:custom_method), ActiveResource returns a hash instead of object that you can call with methods.
Update: in the ActiveResource code, all the find methods are calling the private method instantiate_record, which essentially just does a Base.new(hash_returned_from_get). There's also a similar private method instantiate_collection.
Unfortunately, these are private methods - it would be great if they were protected, so we could call them from our subclasses. In the meantime, I just use the dirty dirty workaround:
def get_record(path)
MyActiveResource.new(get(path))
end
Calling it with
MyActiveResource.find(1). get_record(:custom_method)
@Ryan Townsend
@Ryan Bates
@Arya
I got around the authentication by doing the following:
class Pie < ActiveResource::Base
self.site = "https://api.pie.com/"
def self.for_user(user)
self.site = "https://#{user}@api.pie.com/"
return self
end
end
rs = Pie.for_user(1).find(:all)
puts rs
One problem though is that you have to make sure after you use it to reset self.site to the correct url, otherwise there could be a security breach and you could access someone elses stuff elsewhere in your code. Something like this should really get built into ActiveResource
Am I off my rocker or is any solution that modifies self.site going to be un-thread-safe?
If you have 3 processes accessing 3 different sites (different authentications) then when you modify self.site for any one of them, you're either going to have to lock out other processes until you can "reset" it or you're going to allow each unique process to use a random authentication willy nilly.
@John Bracy
I guess you could use a block to ensure the self.site thing gets set back to what it was before, without you having to remember to reset it.
something like:
class Pie < ActiveResource::Base
self.site = "https://api.pie.com/"
def self.for_user(user)
old_site = self.site
self.site = "https://#{user}@api.pie.com/"
yield self
self.site = old_site
end
end
Pie.for_user(1) do |pie|
rs = pie.find(:all)
puts rs
end
Ok, this post is a little old but for those looking for a nice solution to the ActiveResource user login question you can now just use the following syntax. Clean and simple. Just like Rails.
class Pie < ActiveResource::Base
self.site = "http://api.pie.com/"
self.user = username
self.password = password
end
See the Rails API for details.
>>>p.save Don't work<<<
p =Product.find(:last}) # GET products.xml?search=table
p.name = 'bar'
p.save # PUT products/11.xml
# => false
Don't change the name!!!
on the server side throws this msg:
WARNING: Can't mass-assign these protected attributes: id
Did it actually work for you ?
I am having exact problem
when debugged i found out that this might be because when we try to save using active resource xml request
in params: _method=> "put" was not there.
not able to solve this yet..
will appreciate any help
Thanks for this helpful railscast!
If you encounter "EOFError (end of file reached)" on the consumer, and <Mongrel::HttpParserError: Invalid HTTP format, parsing fails.> on the REST provider, you might need a leading slash on the ActiveResource prefix. In this RailsCast, it shows
self.prefix = 'foo/bar/'
Try:
self.prefix = '/foo/bar/'
I've played with ActiveResource a bit recently, and found it lacking. I did, however, write up a little bit about it here on my blog; mostly dealing with hacks (or, as some might say, work-arounds) to deal with shallow nested routes.
@Derano
Thanks for the Tip on nested ActiveResource!
Cool blog. I look forward to returning. Luck!
It would be intresting for me to know who has won this contest...?
Thanks for the screen cast. this is very helpful.
I have problem. I'm using
rails 3 ,
device 1.2.rc ,
token_authentication
I need to create a new object and save in the database through active resource.
book = Book.create(:name=>'abc',:auth_token=>'423423423423')
but this returned 401 error. (unautherized). if I removed the authenticate_user! command, then this will work.
but I need to create a row with auth-token. is there any one done this?.
please reply.
thanks
I was having the same problem as comments #19 and 20 with custom query methods returning hashes rather than objects. I didn't find what I was looking for in the online docs, but looking at activeresource-2.3.5/lib/active_resource/base.rb line 544 I found a :from option to find. Using the syntax from the provided examples, I can get back a collection of objects from a custom query. The custom query just needs to send back a naked array of valid JSON objects. For example:
[{"name": "John", "position": 1}, {"name": "Jane", "position": 2}]
Person.find(:all, :from => "/companies/1/people.xml")
For anyone who wants to add api key on every request with ActiveResource see my hack here => http://stackoverflow.com/questions/2918419/add-api-key-to-every-request-in-activeresource/6124110#6124110
If you are better solution please send me ! With Rack Middleware for example... => http://stackoverflow.com/questions/6046705/add-api-key-on-every-request-with-rack-middleware
Enjoy
Hi There!
Is it possible to forward a cookie from the user browsing my site, trough ActiveResource? So that the site in self.site receives the cookie from my user?
Regards,
Nadeem
Does anybody know answer?
http://stackoverflow.com/questions/13176487/non-rails-style-format-of-xml-response-how-to-parse-with-activeresource-correctl
Thank you