• This repository has been archived on 18/Apr/2018
  • Stars
    star
    354
  • Rank 120,042 (Top 3 %)
  • Language
    Ruby
  • License
    MIT License
  • Created almost 13 years ago
  • Updated over 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Model and repository framework

Curator

Build Status Gem Version

See Untangle Domain and Persistence Logic with Curator for the announcement blog post.

Curator is a model and repository framework for Ruby. It's an alternative to ActiveRecord-like libraries where models are tightly coupled to persistence. Curator allows you to write domain object that are persistence free, and then write repositories that persist these objects. These ideas are largely taken from the Repository section of Domain Driven Design.

Currently, curator supports Riak, MongoDB and an in-memory data store for persistence. If you are interested in enhancing curator to support other data stores, please let us know.

Usage

Domain objects should include the Curator::Model module:

class Note
  include Curator::Model
  attr_accessor :id, :title, :description, :user_id
end

These models can be intiatiated with hashes and used just like regular ruby objects:

note = Note.new(:title => "My Note", :description => "My description")
puts note.description

Repositories should include the Curator::Repository module:

class NoteRepository
  include Curator::Repository
  indexed_fields :user_id
end

Repositories have save, find_by_id, and find_by and find_first_by methods for indexed fields. find_by methods return an array of all matching records, while find_first_by only returns the first match (with no ordering):

note = Note.new(:user_id => "my_user")
NoteRepository.save(note)

note1 = NoteRepository.find_by_id(note.id)
note2 = NoteRepository.find_first_by_user_id("my_user")
my_notes = NoteRepository.find_by_user_id("my_user")

Fields included in indexed_fields are indexed in both Riak and MongoDB when persisted.

As persistence gets more complicated, repositories can implement their own serialize and deserialize methods to handle any case. For example, if our note contained a PDF, the repository might look like:

class NoteRepository
  include Curator::Repository

  indexed_fields :user_id

  def self.serialize(note)
    attributes = super(note)
    attributes[:pdf] = Base64.encode64(note.pdf) if note.pdf
    attributes
  end

  def self.deserialize(attributes)
    note = super(attributes)
    note.pdf = Base64.decode64(attributes[:pdf]) if attributes[:pdf]
    note
  end
end

Rails

See curator_rails_example for an example application using curator.

If you use curator within Rails, all you need is to add curator to your Gemfile and create a config/riak.yml with contents like:

development:
  :http_port: 8098
  :host: localhost
test:
  :http_port: 8098
  :host: localhost

We recommend putting your models in app/models and your repositories in app/repositories. If you do this, don't forget to add app/repositories to the list of autoload paths:

# config/application.rb

config.autoload_paths += %W(#{config.root}/app/repositories)

You can also use Rails form builder with curator models:

<%= form_for @note, :url => { :action => "create" } do |f| %>
  <dl>
    <dt><%= f.label :title %></dt>
    <dd><%= f.text_field :title %></dd>
    <dt><%= f.label :description %></dt>
    <dd><%= f.text_area :description, :size => "60x12" %></dd>
  </dl>
  <%= f.submit "Create" %>

Without Rails

If you are not using Rails, you can configure curator manually:

Curator.configure(:riak) do |config|
  config.bucket_prefix = "my_app"
  config.environment = "development"
  config.migrations_path = File.expand_path(File.dirname(__FILE__) + "/../db/migrate")
  config.riak_config_file = File.expand_path(File.dirname(__FILE__) + "/config/riak.yml")
end

Alternatively, instead of using a YAML file to configure the client you can set it manually.

Curator.configure(:riak) do |config|
  config.bucket_prefix = "my_app"
  config.environment = "development"
  config.migrations_path = File.expand_path(File.dirname(__FILE__) + "/../db/migrate")
  config.client = Riak::Client.new
end

Testing

If you are writing tests using curator, it's likely that you will want a way to clean up your data in Riak between tests. Riak does not provide an easy way to clear out all data, so curator takes care of it for you. You can use the following methods if you change your backend from :riak to :resettable_riak:

  • remove_all_keys - remove everything in Riak under the current bucket_prefix and environment
  • reset! - remove all keys since the last reset!

For example, our spec_helper.rb file looks like this for our rspec test suite:

Curator.configure(:resettable_riak) do |config|
  config.bucket_prefix = "curator"
  config.environment = "test"
  config.migrations_path = File.expand_path(File.dirname(__FILE__) + "/../db/migrate")
  config.riak_config_file = File.expand_path(File.dirname(__FILE__) + "/../config/riak.yml")
end

RSpec.configure do |config|
  config.before(:suite) do
    Curator.data_store.remove_all_keys
  end

  config.after(:each) do
    Curator.data_store.reset!
  end
end

This ensures that our tests start with an empty Riak, and the data gets removed in between tests.

Data Migrations

See Data migrations for NoSQL with Curator for an overview of data migrations. They have also been implemented in the curator_rails_example.

Each model instance has an associated version that is persisted along with the object. By default, all instances start at version 0. You can change the default by specifying the current_version in the model class:

class Note
  current_version 1
end

note = Note.new
note.version #=> 1

When the repository reads from the data store, it compares the stored version number to all available migrations for that collection. If any migrations are found with a higher version number, the attributes for the instance are run through each migration in turn and then used to instantiate the object. This means that migrations are lazy, and objects will get migrated as they are used, rather than requiring downtime while all migrations run.

In order to write a migration, create a folder with the collection name under the migrations_path that was configured in the Curator.configure block.

mkdir db/migrate/notes/

Then, create a file with a filename that matches #{version}_#{class_name}.rb:

# db/migrate/notes/0001_update_description.rb

class UpdateDescription < Curator::Migration
  def migrate(attributes)
    attributes.merge(:description => attributes[:description].to_s + " -- Passed through migration 1")
  end
end

Now, all Note objects that are read with a version lower than 1 will have their description ammended. Migrations are free to do what they want with the attributes. They can add, edit or delete attributes in any combination desired. All that matters is that the resulting attributes hash will be used to instantiate the model.

Since migrations merely accept and return a hash, they are easy to unit test. They do not affect the data store directly (like ActiveRecord migrations), so there is no harm in calling them in tests:

require 'spec_helper'
require 'db/migrate/notes/0001_update_description'

describe UpdateDescription do
  describe "migrate" do
    it "appends to the description" do
      attributes = {:description => "blah"}
      UpdateDescription.new(1).migrate(attributes)[:description].should == "blah -- Passed through migration 1
    end
  end
end

Repository configuration

Repositories allow their default collection properties to be configured through a syntax similar to Sinatra's configuration interface. Configurable properties are data store specific and currently Riak is the only supported default-overridable data store.

The methods used to configure the Repository are set, enable, and disable:

class NoteRepository
  include Curator::Repository

  set :n_val, 5
  enable :allow_mult
  disable :last_write_wins
end

For more information on the properties that can be configured for Riak buckets, see this documentation.

Applying the configuration

Repository configuration is not applied automatically. To apply the uncommitted properties, run the curator:repository:apply rake task:

$ rake curator:repository:apply
[Curator] Preparing to apply settings to all repositories...
 * Updating settings for NoteRepository... Done!
[Curator] Done!

To manually apply uncommitted properties to a specific repository, use NoteRepository.apply_settings! in an irb or Rails console session.

Under the hood

Curator stores objects in the data store using the id as the key. The value is a json representation of the instance_values of the object. Your repository can implement serialize/deserialize to get different behavior.

Riak

The bucket name in Riak is <bucket_prefix>:<environment>:<collection>. The bucket prefix is configurable. By default, it will either be curator or the name of the Rails application if you are using curator within Rails. The collection is derived from the name of the Repository class, and it can be overriden. For example, if you implement a NoteRepository, the riak bucket will be curator:development:notes in development mode, and curator:production:notes in production mode.

MongoDB

The collection name in MongoDB is derived from the name of the Repository class, and it can be overriden. For example, if you implement a NoteRepository, the collection name will be notes.

MongoDB will preserve the types of attributes. For example, if you set an attribute as a Time object, it will come back as a Time object. If you do not set a key for an object, MongoDB will generate one as a BSON::ObjectId, which will be returned. If you want to find_by_id, you will have to use the BSON::ObjectId class, not a String. On the other hand, if you specify keys as strings, you can look them back up as strings.

Contributing

We appreciate contributions of any kind. If you have code to show us, open a pull request. If you found a bug, want a new feature, or just want to talk design before submitting a pull request, open an issue.

Please include tests with code contributions, and try to follow conventions that you find in the code.

Riak is required in order to run the curator specs. After installing Riak, change the backend to leveldb. For example, here is how to install on OS X using homebrew:

brew install riak
edit /usr/local/Cellar/riak/<riak version>/libexec/etc/riak.conf

  change
    storage_backend = bitcask
  to
    storage_backend = leveldb

riak start

Writing new data stores

Curator has a set of shared_examples for data store specs. Take a look at spec/curator/shared_data_store_specs.rb. These cover most of the data store functionality, so include these on your spec and make them pass:

require 'spec_helper'
require 'curator/shared_data_store_specs'

module Curator::SomeNewDB
  describe Curator::SomeNewDB::DataStore do
    include_examples "data_store", DataStore

    ... other specs specific to SomeNewDB ...
  end
end

License

Curator is released under the MIT license.

More Repositories

1

manners

A polite Go HTTP server that shuts down gracefully.
Go
987
star
2

credit-card-type

A library for determining credit card type
TypeScript
916
star
3

card-validator

Validate credit cards as users type.
TypeScript
822
star
4

runbook

A framework for gradual system automation
Ruby
700
star
5

braintree_ios

Braintree SDK for iOS
Swift
542
star
6

braintree_php

Braintree PHP library
PHP
535
star
7

braintree_ruby

Braintree Ruby library
Ruby
430
star
8

braintree_android

Braintree SDK for Android
Kotlin
406
star
9

android-card-form

A ready-made card form layout that can be included in your Android app, making it easy to accept credit and debit cards.
Java
360
star
10

braintree_node

Braintree Node.js library
JavaScript
325
star
11

sanitize-url

TypeScript
253
star
12

braintree_python

Braintree Python library
Python
235
star
13

pg_ha_migrations

Enforces DDL/migration safety in Ruby on Rails project with an emphasis on explicitly choosing trade-offs and avoiding unnecessary magic.
Ruby
182
star
14

braintree_express_example

An example Braintree integration for Express
CSS
175
star
15

jsdoc-template

A clean, responsive documentation template with search and navigation highlighting for JSDoc 3
CSS
172
star
16

framebus

A message bus that operates across iframes
TypeScript
150
star
17

braintree_java

Braintree Java library
Java
147
star
18

braintree_dotnet

Braintree .NET library
C#
132
star
19

braintree_php_example

An example Braintree integration for PHP
CSS
124
star
20

braintree-android-drop-in

Braintree Drop-In SDK for Android
Java
118
star
21

braintree_flask_example

An example Braintree integration for Flask
CSS
103
star
22

braintree-ios-drop-in

Braintree Drop-in for iOS
Objective-C
97
star
23

braintree_rails_example

An example Braintree integration for Ruby on Rails
HTML
85
star
24

braintree_spring_example

An example Braintree integration for Spring (Java)
CSS
80
star
25

braintree-encryption.js

Javascript Library for Client-side Encryption with Braintree
JavaScript
77
star
26

pg_column_byte_packer

Auto-order table columns for optimize disk space usage
Ruby
71
star
27

restricted-input

Restrict <input>s to certain valid characters (e.g. formatting phone or card numbers)
TypeScript
67
star
28

graphql-api

Schemas, changelogs and feature requests for Braintree's GraphQL API
61
star
29

browser-detection

A utility for detecting browsers in Braintree libs.
TypeScript
60
star
30

braintree_aspnet_example

An example Braintree integration in the ASP.NET framework
CSS
59
star
31

mallory

Reverse proxy for HTTPS services, with SSL verification.
Python
57
star
32

us-bank-account-validator

A library for validating US bank account routing and account numbers
TypeScript
51
star
33

popup-bridge-android

PopupBridge allows WebViews to open popup windows in a browser and send data back to the WebView
Java
49
star
34

popup-bridge-ios

Enable your web view to open pages in a Safari View Controller
Swift
32
star
35

litmus_paper

Backend health tester for HA Services
Ruby
32
star
36

open_api_parser

A parser for Open API specifications
Ruby
30
star
37

big_brother

a daemon to monitor and administer servers in a LVS cluster of load balanced virtual servers
Ruby
28
star
38

mysql_to_postgresql

ruby script which migrates data from a MySQL database to PostgreSQL
Ruby
26
star
39

fake-wallet-app-ios

A fake version of the {PayPal,Venmo} Wallet for development
Objective-C
25
star
40

braintree_android_encryption

braintree_android_encryption
Java
24
star
41

browser-switch-android

Open a url in a browser or Chrome Custom Tab and receive a response as the result of user interaction.
Java
23
star
42

braintree_slim_example

An example Braintree integration for Slim (PHP)
CSS
22
star
43

form-napper

Hijack, submit, and inject data into forms.
JavaScript
21
star
44

braintree_perl

Braintree Perl library
Perl
18
star
45

braintree-auth-example

A Ruby/Sinatra application that demonstrates the Braintree Auth API
JavaScript
17
star
46

curator_rails_example

Example Rails application for curator
Ruby
16
star
47

activerecord-postgresql-citext

citext support for rails 4
Ruby
15
star
48

braintreehttp_php

PHP
15
star
49

wrap-promise

Small module to help support APIs that return a promise or use a callback.
TypeScript
15
star
50

iframer

Create consistent iframes
TypeScript
14
star
51

mallorca

Man-in-the-middle proxying for HTTPS.
JavaScript
14
star
52

braintree_graphql_rails_example

An example Braintree integration with the GraphQL API using Ruby on Rails
HTML
14
star
53

inject-stylesheet

Create a <style> element with CSS properties, filtering input using an allowlist or blocklist.
TypeScript
14
star
54

apollo-tracing-uploader-java

Upload Java GraphQL tracing metrics to Apollo Graph Manager
Java
13
star
55

braintree_client_side_encryption

javascript library for client-side encryption with braintree
JavaScript
13
star
56

braintree-ios-visa-checkout

Visa Checkout component for our Braintree iOS SDK
Objective-C
9
star
57

braintree_android_encryption_examples

Java
8
star
58

braintree-web-bower

JavaScript
8
star
59

braintree-android-visa-checkout

Visa Checkout component for our Braintree Android SDK
Java
7
star
60

spidersuite

Configurable crawler and reporting tool for verifying websites
JavaScript
6
star
61

qsagi

A friendly way to talk to RabbitMQ
Ruby
5
star
62

eslint-config

Shared linting configuration for braintree js projects
TypeScript
5
star
63

braintree-types

TypeScript definitions for Braintree Custom Actions
TypeScript
4
star
64

heckler

Heckler's aim is to allow you to correlate code changes with Puppet noop output!
Go
4
star
65

braintree.github.io

Braintree open source website
HTML
3
star
66

braintreehttp_python

Python
3
star
67

braintreehttp_java

Java
3
star
68

event-emitter

A simple JS based event emitter
TypeScript
3
star
69

asset-loader

A module to load frontend assets.
TypeScript
3
star
70

braintree_windows_phone_encryption

.net library for client-side encryption with braintree
C#
2
star
71

braintreehttp_ruby

Ruby
2
star
72

class-list

A helper for applying classes to dom nodes.
TypeScript
2
star
73

fluent-plugin-s3

Ruby
2
star
74

braintree-android-samsung-pay

Samsung Pay component for our Braintree Android SDK
Java
2
star
75

braintreehttp_node

JavaScript
1
star
76

extended-promise

TypeScript
1
star
77

braintree-web-drop-in-bower

Braintree Drop-in for the web
JavaScript
1
star
78

webhint-configuration-braintree-sdk

Beta Webhint configuration for Braintree's sdk-related packages
1
star
79

destructor

TypeScript
1
star
80

braintree_windows_phone_encryption_examples

C#
1
star
81

credit-card-form

Name TBD
1
star
82

popup-bridge-example

Example site for Popup Bridge mobile library
HTML
1
star
83

uuid

A simple node js implementation of uuid v4 for use with Braintree's JS based SDKs.
JavaScript
1
star