开发者

Implementing ActiveRecord-like associations for an API wrapper

开发者 https://www.devze.com 2023-04-12 00:50 出处:网络
I recently wrote ParseResource, which is a Ruby API wrapper for Parse.com\'s REST api. Here\'s a some basic usage:

I recently wrote ParseResource, which is a Ruby API wrapper for Parse.com's REST api.

Here's a some basic usage:

class Post < ParseResource
  fields :title, :author, :body
end
p = Post.create(:title => "Hello world", :author => "Alan", :body => "ipso lorem")

The project is fairly young, and a feature I really want to implement is associations. Something like this:

class Author < ParseResource
  has_many :posts
  fields :name, :email
end
class Post < ParseResource
  belongs_to :author
  fields :title, :body
end
a = Author.create(:name => "Alan", :email => "alan@example.com")
p = Post.create(:title => "Associated!", :body => "ipso lorem", :author => a)
p.author.class #=> Author
p.author.name #=> "Alan"
a.posts #=> an array of Post objects

I'd love any advice, pointers, and pitfalls from anyone who has implemen开发者_运维技巧ted something similar as well as from anyone who has a grasp of Parse's REST API.


I've found using DataMapper ( http://datamapper.org ) it's pretty easy to get it working with almost any datastore. You can write an adapter that talks to your datastore and then use all of the power of DataMapper directly as if your data was in SQL. Here's a link that explains a bit about writing one of these adapters. http://www.killswitchcollective.com/articles/55_datamapperabstractadapter_101


It looks like Parse works by storing objects as a hash of key-value pairs. So basically you have an id and then a hash with which you can let your imagination run.

To do associations like ActiveRecord you need a primary key (e.g., Author.id) and a foreign key (e.g., Post.author_id). The Author.id is simple - just make it the id of the Parse object. Then store the author id for a post inside the post, keyed by 'author_id'. So that's the data side.

In the code there are several levels of implementation to consider. For retrieval you're aiming to make methods like this:

class Author
  def posts
    @posts ||= Post.find(:all, :id => id)
  end
end

class Post
  def author
    @author ||= Author.find(author_id)
  end
end

That's not too hard and can be done in many ways, for instance using metaprogramming. Harder is the save. What you're aiming for, at least from the Author side, is something like this:

class Author
  def after_save
    super
    posts.each do |p|
      p.author_id = id
      p.save
    end
  end
end

Or rather I should say that's what you might be aiming for depending on the scenario. One of the pitfalls in implementing associations is deciding when to do stuff. You don't want to complicate your life, but you also don't want to go crazy with API calls. Consider simply updating the name of an author:

a = Author.find(1)
a.name = "Joe"
a.save

As written after_save will load existing posts (it goes through posts which sets @posts), set author_id on each post (which need not be done in this case), and then save the posts even though nothing has changed them. Moreover what if a post fails during save? In that case transactions are needed so you can rollback the whole thing and prevent an inconsistent state.

You can see in the ActiveRecord code there is a ton of logic surrounding the issue of how to handle children when a parent is saved. The result is the slick and transparent associations but all kinds of other things get involved; proxies, association classes, etc.

My advice is this. Decide if you really need slick and transparent associations. If not then metaprogram a few accessors and convenience methods and leave it at that. Otherwise, spend time studying the ActiveRecord association code directly or consider DataMapper which, AFAIK, gives you an ActiveRecord-like interface, including associations, with the ability to change data stores.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号