public
Description: Ribs is a Ruby DAO/ORM framework built on top of Hibernate, running on JRuby.
Homepage:
Clone URL: git://github.com/olabini/ribs.git
ribs / lib / ribs / repository.rb
100644 245 lines (215 sloc) 7.898 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
module Ribs
  # Gets a Repository object for the object in question. If the object
  # is a class, the repository returned will be a Repository::Class,
  # otherwise it will be a Repository::Instance. Specifically, what
  # you will get for the class FooBar will be the class
  # Ribs::Repository::DB_default::FooBar and you will get an instance
  # of that class if the object is an instance of FooBar. This allows
  # you to add specific functionality to these repositories. The
  # class Ribs::Repository::DB_default::FooBar will also include
  # Ribs::Repository::FooBar and extend
  # Ribs::Repository::FooBar::ClassMethods so you can add behavior to
  # these that map over all databases.
  def self.Repository(obj, db = :default)
    Ribs.execute_delayed_ribs!
    db_name = "DB_#{db}"
    model_type = case obj
          when Class
            obj
          else
            obj.class
          end
 
    dbmod = Ribs::Repository::const_get(db_name)
    name = model_type.name.split(/::/).join("_")
 
    if !dbmod.constants.include?(name)
      Repository::create_repository(name, dbmod)
    end
    
    cls = dbmod::const_get(name.to_sym)
    Ribs::Repository.ensure_repository(name, cls, model_type, db)
 
    return cls if obj.kind_of?(Class)
 
    ret = cls.allocate
    ret.send :initialize
    ret.instance_variable_set :@database, db
    ret.instance_variable_set :@model, obj
    ret
  end
  
  # A Repository is the main gateway into all functionality in
  # Ribs. This is where you send your objects to live in the DB, etc.
  #
  # A Repository is a combination implementation of both Data Mapper and Repository.
  module Repository
    # The ClassMethods are everything that's available when getting
    # the repository for a specific model class. It includes most of
    # the things you'd expect to do on the class itself in
    # ActiveRecord - stuff like finders, creators and things like
    # that.
    module ClassMethods
      include Repository
 
      # Get the meta data for this model
      def metadata
        @metadata
      end
 
      # Define accessors for this model
      def define_accessors
        self.metadata.properties_and_identity.each do |name, _|
          self.model.send :attr_accessor, name.downcase
        end
      end
      
      # Makes a specific instance of this class be marked persistent
      def persistent(obj)
        (@persistent ||= {})[obj.object_id] = true
      end
      
      # Checks if a specific instance is marked as persistent
      def persistent?(obj)
        @persistent && @persistent[obj.object_id]
      end
 
      # Makes a specific instance of this class be marked destroyed
      def destroyed(obj)
        (@destroyed ||= {})[obj.object_id] = true
      end
      
      # Checks if a specific instance is marked as destroyed
      def destroyed?(obj)
        @destroyed && @destroyed[obj.object_id]
      end
 
      # Create a new instance of this model object, optionally setting
      # properties based on +attrs+.
      def new(attrs = {})
        obj = self.model.new
        attrs.each do |k,v|
          obj.send("#{k}=", v)
        end
        obj
      end
 
      # First creates a model object based on the values in +attrs+ and
      # then saves this to the database directly.
      def create(attrs = {})
        val = new(attrs)
        R(val, self.database).save
        val
      end
      
      # Returns all instances for the current model
      def all
        Ribs.with_handle(self.database) do |h|
          h.all(self.metadata.persistent_class.entity_name)
        end
      end
 
      # Will get the instance with +id+ or return nil if no such entity
      # exists.
      def get(id)
        Ribs.with_handle(self.database) do |h|
          h.get(self.metadata.persistent_class.entity_name, id)
        end
      end
 
      # Destroys the model with the id +id+.
      def destroy(id)
        Ribs.with_handle(self.database) do |h|
          h.delete(get(id))
        end
      end
    end
    
    # InstanceMethods are those that become available when you the
    # repository for a specific instance. This allows things like
    # saving and destroying a specific instance.
    module InstanceMethods
      include Repository
      
      # Get the meta data for this model
      def metadata
        self.class.metadata
      end
 
      # Removes this instance from the database.
      def destroy!
        self.class.destroyed(self.model)
        Ribs.with_handle(self.database) do |h|
          h.delete(self.model)
        end
      end
 
      # Saves this instance to the database. If the instance already
      # exists, it will update the database, otherwise it will create
      # it.
      def save
        Ribs.with_handle(self.database) do |h|
          if self.class.persistent?(self.model)
            h.update_obj(self.model)
          else
            h.insert_obj(self.model)
            self.class.persistent(self.model)
          end
        end
        self.model
      end
    end
 
    # Every repository is tied to a specific database
    attr_reader :database
    # Every repository is tied to a specific model. This is either a
    # class or an instance of a class
    attr_reader :model
    
    class << self
      # Makes sure the class sent in is actually a repostiroy. It
      # checks all invariants for a Repository and makes them true if
      # they aren't already.
      def ensure_repository(name, cls, real, db)
        unless cls.kind_of?(Repository)
          mod1 = if Repository.constants.include?(name)
                   Repository.const_get(name)
                 else
                   mod = Module.new
                   Repository.const_set(name, mod)
                   mod
                 end
          
          unless mod1.kind_of?(Repository)
            mod1.send :include, Repository::InstanceMethods
          end
 
          unless mod1.constants.include?("ClassMethods")
            mod1.const_set(:ClassMethods, Module.new)
          end
          
          cls.send :include, mod1
          cls.send :extend, mod1.const_get(:ClassMethods)
          cls.send :extend, Repository::ClassMethods
          cls.instance_variable_set :@database, db
          cls.instance_variable_set :@model, real
          cls.instance_variable_set :@metadata, Ribs::metadata_for(db, real, cls)
        end
      end
 
      # Create a repository for a model inside a database
      def create_repository(name, dbmod)
        c = Class.new
        mod1 = if Repository.constants.include?(name)
                 Repository.const_get(name)
               else
                 mod = Module.new
                 Repository.const_set(name, mod)
                 mod
               end
        dbmod.const_set name, c
      end
      
      # Dynamically create new database modules
      def const_missing(name)
        if /^DB_(.*?)$/ =~ name.to_s
          db_name = $1
          mod = Module.new
          mod.instance_variable_set :@database_name, db_name.to_sym
          const_set name, mod
          mod
        else
          super
        end
      end
    end
  end
end
 
module Kernel
  # Gets a Repository object for the object in question. If the object
  # is a class, the repository returned will be a Repository::Class,
  # otherwise it will be a Repository::Instance. Specifically, what
  # you will get for the class FooBar will be the class
  # Ribs::Repository::DB_default::FooBar and you will get an instance
  # of that class if the object is an instance of FooBar. This allows
  # you to add specific functionality to these repositories. The
  # class Ribs::Repository::DB_default::FooBar will also include
  # Ribs::Repository::FooBar and extend
  # Ribs::Repository::FooBar::ClassMethods so you can add behavior to
  # these that map over all databases.
  def R(obj, db=:default)
    Ribs::Repository(obj, db)
  end
end