I'm building an application that consumes models through an api with ActiveResource. I noticed that the
#resource ||= #resource.do a query
doesn't work, i.e. If I put something like that in my controller, my application will still query the api. So there is no built in caching that I'm used to with ActiveRecord. Time to expand my knowledge and skill base, ok.
I found this: http://injectisforwizards.com/blog/read-through-caching-of-activeresource/, and while I don't understand this 100% yet, for controller based queries that do .find, this appears to work. But not for any custom queries I have e.g.:
#current_resource ||= Resource.get(:resource_all, :by_account=>#current_account.account_key)
(which hits a custom controller and runs a scope, returning a collection)
I'm working through this and I'll find out what is going, but I'm curious if someone could tell me simpler terms what is going on, what I can do to smooth over caching in ActiveResource to be more like ActiveRecord, how I can tailor this to cache all queries, etc. Anything really would be helpful.
EDIT:
I found this: https://github.com/Ahsizara/cached_resource which looks promising but it is new (and built off that link above)....notable is that it does not seem to handle any sort of collections, but for one resource finds/caches well.
Since you receive a new instance of a controller for each request, caching like that is not going to work. I'm presuming you have something like this:
def show
#resource ||= expensive_request
end
What are you expecting #resource to be when that method executes? The result of something from a previous call to show? Not going to happen.
What you need to do is put things into Rails.cache if you want them to persist between requests. If you use memcached as a back end, this can be very efficient. See the Caching with Rails guide for more information around section 2.5.
In general, a better approach than the ||= method of caching is to define protected methods that are wrapped with memoize to save the results. For example:
def load_resource
expensive_request
end
memoize :load_resource
Remember that this will only apply to subsequent calls to that method within the context of the same request and is not the same as using Rails.cache.
Related
The other day I stumbled on Sandi Metz's rules, and one of them reads
When a call comes into your Rails controller, you can only instantiate one object to do whatever it is that needs to be done.
Still pretty new to Rails, but I always thought that my controller methods had some smell in them, and this confirmed it. I have a dashboard view for a parent model that displays information about their children (a different model) and the children's challenges(another model), all with different controllers. Here is an example of one of our controller methods.
def dash
#parent = current_user
#children = #parent.children
#completed_challenges = #parent.assigned_challenges.where("parent_id =?", #parent.id).where("completed =?", true)
#validated_challenges = #parent.assigned_challenges.where("parent_id =?", #parent.id).where("validated =?", true)
#enabled_rewards = #parent.enabled_rewards.where("parent_id =?", #parent.id)
end
I was wondering if I could send multiple requests to get all of these objects from their respective controllers as opposed to lumping them all in one request. I know I can do this with Ajax, but is there a way just doing multiple http requests when the page is loading?
I appreciate the help!
The answer is NO.
AJAX was built specifically for that purpose, to overcome the short-comings of multiple http requests. Plus multiple http requests while page load is frowned upon because of the performance slump. It might seem lucrative path for a small project, but when it scales, you will definitely run into huge holes.
Though I am not a big fan of it, but following the beaten path can save you a lot of effort in this case. :)
The recommended approach is using the Facade Pattern. A perfect example of how to handle this is provided by Thoughtbot in the "Only instantiate one object in the controller" section of this blog post:
http://robots.thoughtbot.com/sandi-metz-rules-for-developers
When a call comes into your Rails controller, you can only instantiate one object to do whatever it is that needs to be done.
I see the usefulness of this rule, but I think you can interepret it in a slightly different way. When you load a controller, you do want it doing as little as possible. This is a given.
In your case, though, you seem to have a lot of different bits of data to load. Besides looking ugly, you'll see that the code also tends to get bigger and bigger as your dashboard supports more and more metrics. You don't really want to be in a situation where the number of lines of code increases proportionally with the amount of data an application shows.
Instead, send one object - a dictionary of all the data that you need to show. And I'd suggest moving the construction of that dictionary off to a model (or better still, a service) that builds it up (possibly based on configuration of some sort). The controller should just be fetching that object from somewhere and sending it back in the correct format.
I am reading the documentation for the Tire gem and I am a confused about what it mean by the following paragraphs. Could someone explain it?
In fact, all this time you've been using only proxies to the real Tire
methods, which live in the tire class and instance methods of your
model. Only when not trampling on someone's foot — which is the
majority of cases — will Tire bring its methods to the namespace of
your class.
So, instead of writing Article.search, you could write
Article.tire.search, and instead of #article.update_index you could
write #article.tire.update_index, to be on the safe side. Let's have a
look on an example with the mapping method:
It means just what it says: Tire tries hard not to drag methods into your model/namespace, and defines its methods only when they don't exist.
As a regular user, you don't have to care about it much. Whenever you call MyModel.search or MyModel.mapping you can also call MyModel.tire.search or MyModel.tire.mapping.
to piggy back off what Karmi has said, you might find this useful if for example your Model already has a search defined on it.
So if say, Post.search already does something in your rails app, you can just use Post.tire.search to do a tire search instead. I like to use it to signal to all developers who might work in the code after I do, that this is a tire method, so they (hopefully) don't spend time wondering where to find a search method in the Post model.
I have been working on implementing my own ORM. And I was wondering how the rails path helper extracts the ID from the object. For example how would I make this work for my ORM?
#contact = Contact.first
contact_path(#contact)
Any help would be greatly appreciated!
Update:
My object does have an ID attribute and responds to it. But yet the path helper returns an error.
In a nutshell you want to be activemodel compliant. This will make url helpers, form_for(#contact) and so on work.
You also get to (optionally) use a bunch of modules dealing with
things such as validations, dirty attributes etc.
There are only a handful of methods you have to implement. There's also an
ActiveModel::Lint module that tests that your implementations of these
primitives are valid, and which also serves as documentation. In particular you need to implement to_param and persisted?. I think some of the naming stuff only gets used if you do stuff like link_to 'foo', #contact
The method checks to see if you've passed it an object, or an integer. If it's an object and that object has an id method (respond_to?(:id)), it uses its ID. Pretty dead simple.
Before I begin, this is basically a port of an existing ASP.MVC REST style service to Sinatra. I am fairly new to Ruby so dont really know the best practises yet, and the web seems a bit torn on the subject.
So currently in the ASP flavour we have some controllers like so:
public class MyController : Controller
{
[Authenticate]
public ActionResult AccessSecretStuffs()
{...}
public ActionResult AccessPublicStuffs()
{...}
}
Now we use a filter to make sure that any actions with Authenticate check for authentication from the current user before loading the action. The routes setup for these are fairly vanilla, they just map to the controller and actions.
Now in Ruby land using Sinatra you dont tend to have controllers, everything is an action, and as we are purely looking to expose data in a restful way this seems great. However I need to be able to make sure that whenever certain routes are called with Sinatra that it will basically call a bit of code first to check if they can access the action, then if not redirect them to the login page.
I was thinking of just putting in some AOP to cover this, but after a bit of reading everyone says Ruby doesnt need AOP and already provides most of the functionality out of the box. So can anyone shed some light on the best practise for doing this?
Sinatra documentation has a before method that would be applicable, but then I would need to do a before method for every other route method, which isnt ideal. In my mind I just want to declare a route, and then put some sort of annotation/attribute at the end which indicates that something needs to happen...
Hope that makes some sort of sense :)
Ruby doesn't have annotations as such, but Sinatra does provide custom filtering mechanisms.
When defining a route, you can put additional conditions, such as various header values and whatnot. You can also define custom conditions:
set(:auth) do |*roles| # <- notice the splat here
condition do
unless logged_in? && roles.any? {|role| current_user.in_role? role }
redirect "/login/", 303
end
end
end
Then you can define a route like this:
get "/secret", :auth => [:user, :admin] do
...
end
or if you don't need to pass any arguments, before block can take a path as an argument:
before '/secret/*' do
authenticate!
end
You can read more at Sinatra readme page
So Candy is a really simple library for interacting with Mongo in Ruby.
My poor SQL brain is having a tough time figuring out how I should map out this problem:
There are users, there are things. Each thing was made by one user, but should be accessible to a subset of all users (specified within the thing). Leaving the specification of user out of the way for now, how would I get a list of all things that user X has access to?
class Thing
include Candy::Piece
end
class Things
include Candy::Collection
collects :thing
end
Should I assign the allowed users to a thing like this? (lets just use strings to reference users for now)
t = Thing.new
t.allowed = ['X','Y','Z']
This seems about right to me, which would make me want to do:
Things.find(allowed:'X')
but it's not quite working…
NoMethodError: undefined method ‘call’ for {:allowed=>"X"}:Hash
any ideas?
I'm really sorry I took so long to catch this and respond. This might be too late for your purposes, but:
Candy doesn't implement a find method. This is on purpose: if an object represents a collection, every access is implicitly finding something in that collection. It's the same reason there is no save method. If the mapping is truly transparent, verbs that mean "Do this in the database" shouldn't be necessary.
So to do what you want, you could either just make a new Things object with the scope passed on creation:
x_is_allowed = Things.new(allowed: 'X')
...or you could save a step and do it by class method:
x_is_allowed = Things.allowed('X')
...or you could start with the whole collection and limit it by attribute later:
things = Things.new
x_is_allowed = things.allowed('X')
So... Um. All of those will work. But. I have to warn you that I'm really not happy with the general usability of Candy right now, and of collections and array fields in particular. The biggest problem is accessors: the [] method isn't working like you'd expect, so you end up having to call to_a and refresh and other things that feel sticky and unpleasant.
This needs to be fixed, and I will do so as soon as I finish the driver rewrite (a related project called Crunch). In the short term, Candy is probably best viewed as an experiment for the adventurous, and I can't guarantee it'll save time until the interface is locked down a bit better. I'm sorry about that.