ribs 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -14,6 +14,11 @@ you to use Hibernate to persist your Ruby objects. There are many
14
14
  things planned for Ribs, but currently it only supports quite basic
15
15
  operations.
16
16
 
17
+ Ribs is explicitly defined to solve several data access patterns for
18
+ most Ruby development. In one end it means scaling down from something
19
+ very much like ActiveRecord, but on the other end supports such things
20
+ as Repository, Data Mapper, Unit of Work and Identity Map.
21
+
17
22
  == Definitions
18
23
 
19
24
  To get started, you first need to define a database connection for
@@ -37,15 +42,25 @@ See the define method and the Ribs::DB class for more information on
37
42
  what's available.
38
43
 
39
44
  To actually make a Ruby class a database backed objects, you use the
40
- method {Kernel#Ribs!}[link:classes/Kernel.html#M000038] In the simple case you don't need to actually
41
- provide any configuration, but support is available for renaming
42
- columns, deciding which one is the primary key, and also avoid making
43
- columns part of the object. The simple case looks like this:
45
+ method {Kernel#Ribs!}[link:classes/Kernel.html#M000038] In the simple
46
+ case you don't need to actually provide any configuration, but support
47
+ is available for renaming columns, deciding which one is the primary
48
+ key, and also avoid making columns part of the object. The simplest
49
+ case looks like this:
44
50
 
51
+ class Blog
52
+ end
53
+
54
+ That is more or less the same as doing
55
+
45
56
  class Blog
46
57
  Ribs!
47
58
  end
48
59
 
60
+ This method call will not do anything that won't be done implicitly at
61
+ the first usage of it as a Ribs model. This means that if you follow
62
+ conventions exactly, you don't need to configure anything at all.
63
+
49
64
  It can also be written as:
50
65
 
51
66
  class Blog; end
@@ -65,22 +80,31 @@ To redefine which table to use, the names of the columns, and where
65
80
  the primary key is, you need to provide a block to the {Ribs!}[link:classes/Kernel.html#M000038] method:
66
81
 
67
82
  class Blog
68
- Ribs! do |r|
69
- r.table :blogs
70
-
71
- r.primary_key :blog_id
83
+ Ribs! :table => :blogs do |r|
84
+ r.blog_id.primary_key!
72
85
 
73
- r.col :title, :blog_title
86
+ r.blog_title :column => :title
74
87
 
75
- r.avoid :irrelevant_column
88
+ r.irrelevant_column.avoid!
76
89
  end
77
90
  end
78
91
 
79
92
  This code will back the model against the "blogs" table, have the
80
93
  column name blog_id represent the primary key, and map the column
81
- title to the property blog_title. Note that primary_key[link:classes/Ribs/Rib.html#M000028] can take a
82
- second parameter that is the property name. Finally, avoid[link:classes/Ribs/Rib.html#M000029] tells Ribs
83
- to avoid a specific column.
94
+ title to the property blog_title. Finally, avoid! tells Ribs
95
+ to avoid a specific column, so it won't try to map that.
96
+
97
+ If you have a primary key that you want to have a different name, or a
98
+ column you want to avoid but it's not nullable, you can use these
99
+ variations instead:
100
+
101
+ class Blog
102
+ Ribs! :table => :blogs do |r|
103
+ r.blog_id :primary_key, :column => :id
104
+
105
+ r.irrelevant_column :avoid, :default => "value to insert:
106
+ end
107
+ end
84
108
 
85
109
  Currently Ribs only supports simple data types. It doesn't include
86
110
  associations, and have no support for setting default values, or
@@ -93,46 +117,52 @@ Once you have a defined model, you can work with it in several
93
117
  different ways.
94
118
 
95
119
  If you want to create new instances you can do it much like with
96
- ActiveRecord:
120
+ ActiveRecord, except that you always need to surround a call to
121
+ anything regarding the database with an invocation to the method
122
+ R. This method takes either a class or an instance, and returns a
123
+ repository proxy for the argument. You can also send in a specific
124
+ database name if you want to work with another database then the
125
+ default one.:
97
126
 
98
127
  blog = Blog.new
99
128
  blog.blog_id = 1
100
129
  blog.blog_title = "Foobar"
101
- blog.save
130
+ R(blog).save
102
131
 
103
- The new method can take the parameters as a hash of symbols too:
132
+ The new-method returned from R can take the parameters as a hash of
133
+ symbols too:
104
134
 
105
- blog = Blog.new(
135
+ blog = R(Blog).new(
106
136
  :blog_id => 1,
107
137
  :blog_title => "Foobar")
108
- blog.save
138
+ R(blog).save
109
139
 
110
140
  And as a short hand a create method is available:
111
141
 
112
- blog = Blog.create(
142
+ blog = R(Blog).create(
113
143
  :blog_id => 1,
114
144
  :blog_title => "Foobar")
115
145
 
116
146
  To find a specific entry:
117
147
 
118
- blog = Blog.find(1)
148
+ blog = R(Blog).get(1)
119
149
 
120
150
  Or to find all entries:
121
151
 
122
- blogs = Blog.find(:all)
152
+ blogs = R(Blog).all
123
153
 
124
154
  To update an entry:
125
155
 
126
156
  blog.blog_title = "New title"
127
- blog.save
157
+ R(blog).save
128
158
 
129
159
  To destroy an existing entry:
130
160
 
131
- blog.destroy!
161
+ R(blog).destroy!
132
162
 
133
163
  Or to destroy it based on id:
134
164
 
135
- Blog.destroy(1)
165
+ R(Blog).destroy(1)
136
166
 
137
167
  == License
138
168
 
data/Rakefile CHANGED
@@ -43,7 +43,7 @@ Gem::manage_gems
43
43
  specification = Gem::Specification.new do |s|
44
44
  s.name = "ribs"
45
45
  s.summary = "Ribs wraps Hibernate, to provide a good ORM for JRuby"
46
- s.version = "0.0.1"
46
+ s.version = "0.0.2"
47
47
  s.author = 'Ola Bini'
48
48
  s.description = s.summary
49
49
  s.homepage = 'http://ribs.rubyforge.org'
Binary file
@@ -22,9 +22,11 @@ require 'ribs.jar'
22
22
  require 'bigdecimal'
23
23
 
24
24
  require 'ribs/db'
25
+ require 'ribs/rib'
25
26
  require 'ribs/definition'
26
- require 'ribs/session'
27
+ require 'ribs/handle'
27
28
  require 'ribs/meat'
29
+ require 'ribs/repository'
28
30
  require 'ribs/core_ext/time'
29
31
 
30
32
  # The Ribs module includes most functionality needed for Ribs. The
@@ -34,21 +36,40 @@ require 'ribs/core_ext/time'
34
36
  module Ribs
35
37
  class << self
36
38
 
37
- # The with_session method provides an easy way of working with a
38
- # low level Ribs session. It will get a session for the database
39
- # in question, yield that session to the block and then release
40
- # the session when finished. This should generally not be needed,
39
+ # The with_handle method provides an easy way of working with a
40
+ # low level Ribs Handle. It will get a handle for the database
41
+ # in question, yield that handle to the block and then release
42
+ # the handle when finished. This should generally not be needed,
41
43
  # but wrapping a block if code with this method is a good way of
42
- # opening a session and make sure it doesn't get fully closed
44
+ # opening a handle and make sure it doesn't get fully closed
43
45
  # until after the block.
44
46
  #
45
- # +from+ decides which database definition to get a session for.
47
+ # +from+ decides which database definition to get a handle for.
46
48
  #
47
- def with_session(from = :default)
48
- s = Ribs::Session.get(from)
49
- yield s
49
+ def with_handle(from = :default)
50
+ h = Ribs::Handle.get(from)
51
+ yield h
50
52
  ensure
51
- s.release
53
+ h.release
54
+ end
55
+
56
+ # Defines a model with the given name, defining attribute
57
+ # accessors and also providing the Ribs mapping from the block
58
+ def define_model(name, options = {}, &block)
59
+ base = Object
60
+ cls = Class.new
61
+ names = name.to_s.split(/::/)
62
+ names[0..-2].each do |nm|
63
+ if !base.constants.include?(nm)
64
+ base.const_set nm, Module.new
65
+ end
66
+ base = base.const_get nm
67
+ end
68
+ base.const_set names.last, cls
69
+
70
+ Ribs!({:on => cls}.merge(options), &block)
71
+ R(cls).define_accessors
72
+ cls
52
73
  end
53
74
  end
54
75
  end
@@ -146,7 +146,7 @@ module Ribs
146
146
  properties.set_property(key, value)
147
147
  end
148
148
  @configuration = Configuration.new.add_properties(properties)
149
- @configuration.set_interceptor org.jruby.ribs.EntityNameInterceptor.new
149
+ @configuration.set_interceptor org.jruby.ribs.RubyInterceptor.new(self, (self.default? ? :default : self.name).to_s)
150
150
  @mappings = @configuration.create_mappings
151
151
  reset_session_factory!
152
152
  end
@@ -157,26 +157,26 @@ module Ribs
157
157
  @session_factory = @configuration.build_session_factory
158
158
  end
159
159
 
160
- # Fetch a new Ribs session connected to the this database. Returns
161
- # a Ribs::Session object.
162
- def session
160
+ # Fetch a new Ribs handle connected to the this database. Returns
161
+ # a Ribs::Handle object.
162
+ def handle
163
163
  sessions = (Thread.current[:ribs_db_sessions] ||= {})
164
164
  if curr = sessions[self.object_id]
165
165
  curr[1] += 1 #reference count
166
- Session.new(self, curr[0])
166
+ Handle.new(self, curr[0])
167
167
  else
168
168
  sess = @session_factory.open_session
169
169
  sessions[self.object_id] = [sess,1]
170
- Session.new(self, sess)
170
+ Handle.new(self, sess)
171
171
  end
172
172
  end
173
173
 
174
- # Release a Ribs::Session object that is connected to this
175
- # database. That Session object should not be used after this
174
+ # Release a Ribs::Handle object that is connected to this
175
+ # database. That Handle object should not be used after this
176
176
  # method has been invoked.
177
- def release(session)
177
+ def release(handle)
178
178
  res = Thread.current[:ribs_db_sessions][self.object_id]
179
- if res[0] == session.hibernate_session
179
+ if res[0] == handle.hibernate_session
180
180
  res[1] -= 1
181
181
  if res[1] == 0
182
182
  res[0].close
@@ -1,80 +1,40 @@
1
1
  module Ribs
2
- # Ribs::ClassMethods contains all the methods that gets mixed in to
3
- # a model class.
4
- module ClassMethods
5
- # Get the metadata for the current model
6
- attr_reader :ribs_metadata
2
+ # Keeps track of defined Ribs
3
+ @ribs = {}
7
4
 
8
- # Create a new instance of this model object, optionally setting
9
- # properties based on +attrs+.
10
- def new(attrs = {})
11
- obj = super()
12
- attrs.each do |k,v|
13
- obj.send("#{k}=", v)
14
- end
15
- obj
16
- end
17
-
18
- # First creates a model object based on the values in +attrs+ and
19
- # then saves this to the database directly.
20
- def create(attrs = {})
21
- val = new(attrs)
22
- val.save
23
- val
24
- end
25
-
26
- # Depending on the value of +id_or_sym+, tries to find the model
27
- # with a specified id, or if +id_or_sym+ is <tt>:all</tt> returns
28
- # all instances of this model.
29
- def find(id_or_sym)
30
- Ribs.with_session do |s|
31
- s.find(self.ribs_metadata.persistent_class.entity_name, id_or_sym)
32
- end
5
+ class << self
6
+ # Returns the specific metadata for a combination of model class
7
+ # and database identifier. If there is no such entry, tries to
8
+ # find the default entry for the model class.
9
+ def metadata(db, model)
10
+ @ribs[[db, model]] || @ribs[model]
33
11
  end
34
12
 
35
- # Destroys the model with the id +id+.
36
- def destroy(id)
37
- Ribs.with_session do |s|
38
- s.delete(find(id))
39
- end
40
- end
41
-
42
-
43
- # TODO: add inspect here
44
- end
45
-
46
- # Ribs::InstanceMethods provides the methods that gets mixed in to
47
- # all instances of a model object.
48
- module InstanceMethods
49
- # Returns the Meat instance for this instance.
50
- def __ribs_meat
51
- @__ribs_meat ||= Ribs::Meat.new(self)
52
- end
53
-
54
- # Returns an inspection based on current values of the data in the
55
- # database.
56
- def inspect
57
- "#<#{self.class.name}: #{self.__ribs_meat.properties.inspect}>"
58
- end
59
-
60
- # Saves this instance to the database. If the instance already
61
- # exists, it will update the database, otherwise it will create
62
- # it.
63
- def save
64
- Ribs.with_session do |s|
65
- s.save(self)
13
+ # Adds metadata for a specific model and an optional database. If
14
+ # the database is nil, it will add the model as a default metadata
15
+ def add_metadata_for(db, model, metadata)
16
+ if db
17
+ @ribs[[db, model]] = metadata
18
+ else
19
+ @ribs[model] = metadata
66
20
  end
67
21
  end
68
22
 
69
- # Removes this instance from the database.
70
- def destroy!
71
- __ribs_meat.destroyed = true
72
- Ribs.with_session do |s|
73
- s.delete(self)
23
+ # Tries to find metadata for the model in question and defines new
24
+ # metadata with define_ribs if no existing metadata exists. This
25
+ # means that you should do your database definitions before using
26
+ # the method R for that model.
27
+ def metadata_for(db, model, from)
28
+ if (v = metadata(db, model))
29
+ return v
74
30
  end
31
+
32
+ metadata = Ribs::MetaData.new
33
+ define_ribs(model, :db => db, :metadata => metadata, :from => from)
34
+ metadata
75
35
  end
76
36
  end
77
-
37
+
78
38
  # Collects all the meta data about a specific Ribs model
79
39
  class MetaData
80
40
  # The table to connect to
@@ -84,7 +44,14 @@ module Ribs
84
44
  attr_accessor :persistent_class
85
45
  # The Rib that defines all the mapping data
86
46
  attr_accessor :rib
47
+ # Should this object be saved in an identity map?
48
+ attr_accessor :identity_map
87
49
 
50
+ # Should this object be saved in an identity map?
51
+ def identity_map?
52
+ self.identity_map
53
+ end
54
+
88
55
  # Return the property instance for the given +name+.
89
56
  def [](name)
90
57
  self.persistent_class.get_property(name.to_s) rescue nil
@@ -93,56 +60,24 @@ module Ribs
93
60
  # Return all the properties for this model.
94
61
  def properties
95
62
  self.persistent_class.property_iterator.to_a.inject({}) do |h, value|
96
- h[value.name] = value
63
+ if !value.respond_to?(:getRubyValue)
64
+ h[value.name] = value
65
+ end
97
66
  h
98
67
  end
99
68
  end
100
- end
101
69
 
102
- # Contains the mapping data that gets created when calling the
103
- # {Ribs!} method.
104
- class Rib
105
- # List of all the columns defined
106
- attr_reader :columns
107
- # List of all primary keys defined
108
- attr_reader :primary_keys
109
- # List of all columns to avoid
110
- attr_reader :to_avoid
111
-
112
- # Initializes object
113
- def initialize
114
- @columns = { }
115
- @primary_keys = { }
116
- @to_avoid = []
117
- end
118
-
119
- # Gets or sets the table name to work with. If +name+ is nil,
120
- # returns the table name, if not sets the table name to +name+.
121
- def table(name = nil)
122
- if name
123
- @table = name
124
- else
125
- @table
70
+ # Return all the properties for this model.
71
+ def properties_and_identity
72
+ (self.persistent_class.property_iterator.to_a + [self.persistent_class.identifier_property]).inject({}) do |h, value|
73
+ if !value.respond_to?(:getRubyValue)
74
+ h[value.name] = value
75
+ end
76
+ h
126
77
  end
127
78
  end
128
-
129
- # Adds a new column mapping for a specific column.
130
- def col(column, property = column, options = {})
131
- @columns[column.to_s.downcase] = [property.to_s, options]
132
- end
133
-
134
- # Adds a new primary key mapping for a column.
135
- def primary_key(column, property = column, options = {})
136
- @primary_keys[column.to_s.downcase] = property.to_s
137
- @columns[column.to_s.downcase] = [property.to_s, options]
138
- end
139
-
140
- # Avoids all the provided columns
141
- def avoid(*columns)
142
- @to_avoid += columns.map{|s| s.to_s.downcase}
143
- end
144
79
  end
145
-
80
+
146
81
  Table = org.hibernate.mapping.Table
147
82
  Column = org.hibernate.mapping.Column
148
83
  Property = org.hibernate.mapping.Property
@@ -156,8 +91,15 @@ module Ribs
156
91
  # The Ruby class
157
92
  attr_accessor :ruby_class
158
93
 
94
+ # Initialize data for this RootClass
159
95
  def initialize(*args)
160
96
  super
97
+ @data = {}
98
+ end
99
+
100
+ # Sets a specific data element
101
+ def []=(key, value)
102
+ @data[key] = value
161
103
  end
162
104
 
163
105
  # Get the Ruby class. Implementation of the WithRubyClass
@@ -165,6 +107,29 @@ module Ribs
165
107
  def getRubyClass
166
108
  @ruby_class
167
109
  end
110
+
111
+ # Gets a specific data element
112
+ def getRubyData(key)
113
+ @data[key]
114
+ end
115
+ end
116
+
117
+ # A Hibernate Property that can contain a Ruby value too.
118
+ class RubyValuedProperty < org.hibernate.mapping.Property
119
+ include org.jruby.ribs.WithRubyValue
120
+
121
+ # The Ruby class
122
+ attr_accessor :ruby_value
123
+
124
+ # Creation
125
+ def initialize(*args)
126
+ super
127
+ end
128
+
129
+ # Implement the interface - return the actual Ruby value
130
+ def getRubyValue
131
+ @ruby_value
132
+ end
168
133
  end
169
134
 
170
135
  class << self
@@ -175,19 +140,34 @@ module Ribs
175
140
  # +options+ have several possible values:
176
141
  # * <tt>:db</tt> - the database to connect this model to
177
142
  #
143
+ # This method is in urgent need of refactoring.
144
+ #
178
145
  def define_ribs(on, options = {})
179
146
  rib = Rib.new
180
147
  yield rib if block_given?
181
148
 
182
- define_metadata_on_class on
183
- rm = on.ribs_metadata
184
- rm.rib = rib
149
+ unless options[:metadata]
150
+ options[:metadata] = Ribs::MetaData.new
151
+ end
185
152
 
153
+ rm = options[:metadata]
154
+ Ribs::add_metadata_for(options[:db], on, rm)
155
+ rm.identity_map = options.key?(:identity_map) ? options[:identity_map] : true
156
+
157
+ unless options[:from]
158
+ options[:from] = R(on, options[:db] || :default)
159
+ end
160
+
161
+ rm.rib = rib
162
+ rib_data = rib.__column_data__
163
+
164
+
186
165
  db = nil
187
- with_session(options[:db] || :default) do |s|
188
- db = s.db
189
- m = s.meta_data
190
- name = rib.table || table_name_for(on.name, m)
166
+ with_handle(options[:db] || :default) do |h|
167
+ db = h.db
168
+ m = h.meta_data
169
+
170
+ name = table_name_for((options[:table] || on.name), m)
191
171
 
192
172
  tables = m.get_tables nil, nil, name.to_s, %w(TABLE VIEW ALIAS SYNONYM).to_java(:String)
193
173
  if tables.next
@@ -222,25 +202,35 @@ module Ribs
222
202
  pc.add_tuplizer(org.hibernate.EntityMode::MAP, "org.jruby.ribs.RubyTuplizer")
223
203
 
224
204
  rm.persistent_class = pc
205
+ rm.persistent_class["meatspace"] = options[:from] if options[:from]
225
206
 
226
207
  table.column_iterator.each do |c|
227
- unless rib.to_avoid.include?(c.name.downcase)
208
+ unless rib_data.to_avoid.include?(c.name.downcase)
228
209
  prop = Property.new
229
210
  prop.persistent_class = pc
230
- prop.name = ((v=rib.columns[c.name.downcase]) && v[0]) || c.name
211
+ prop.name = ((v=rib_data.columns.find{ |key, val| val[0].to_s.downcase == c.name.downcase}) && v[0]) || c.name
231
212
  val = SimpleValue.new(table)
232
213
  val.add_column(c)
233
214
  val.type_name = get_type_for_sql(c.sql_type, c.sql_type_code)
234
215
  prop.value = val
235
-
236
- if (!rib.primary_keys.empty? && rib.primary_keys[c.name.downcase]) || c.name.downcase == 'id'
216
+ if (!rib_data.primary_keys.empty? && rib_data.primary_keys.include?(prop.name)) || c.name.downcase == 'id'
237
217
  pc.identifier_property = prop
238
218
  pc.identifier = val
239
219
  else
240
220
  pc.add_property(prop)
241
221
  end
242
-
243
- define_meat_accessor(on, prop.name)
222
+ else
223
+ if !c.nullable
224
+ prop = RubyValuedProperty.new
225
+ prop.ruby_value = rib_data.default_values[c.name.downcase]
226
+ prop.persistent_class = pc
227
+ prop.name = ((v=rib_data.columns[c.name.downcase]) && v[0]) || c.name
228
+ val = SimpleValue.new(table)
229
+ val.add_column(c)
230
+ val.type_name = get_type_for_sql(c.sql_type, c.sql_type_code)
231
+ prop.value = val
232
+ pc.add_property(prop)
233
+ end
244
234
  end
245
235
  end
246
236
  pc.create_primary_key
@@ -257,21 +247,6 @@ module Ribs
257
247
  JTypes = java.sql.Types
258
248
 
259
249
  private
260
- # Defines the actual accessor for a specific property on a
261
- # class. This will define methods that use the Meat to get data.
262
- def define_meat_accessor(clazz, name)
263
- downcased = name.downcase
264
- clazz.class_eval <<CODE
265
- def #{downcased}
266
- self.__ribs_meat[:"#{name}"]
267
- end
268
-
269
- def #{downcased}=(value)
270
- self.__ribs_meat[:"#{name}"] = value
271
- end
272
- CODE
273
- end
274
-
275
250
  # Returns the Java type for a specific combination of a type name
276
251
  # and an SQL type code
277
252
  def get_type_for_sql(name, code)
@@ -305,25 +280,14 @@ CODE
305
280
  # Tries to figure out if a table name should be in lower case or
306
281
  # upper case, and returns the table name based on that.
307
282
  def table_name_for(str, metadata)
283
+ str = str.to_s.gsub(/::/, '_')
308
284
  if metadata.stores_lower_case_identifiers
309
285
  str.downcase
310
286
  elsif metadata.stores_upper_case_identifiers
311
- str.upcase!
287
+ str.upcase
312
288
  else
313
289
  str
314
290
  end
315
291
  end
316
-
317
- # Defines the meta data information on a class, and mixes in
318
- # ClassMethods and InstanceMethods to it.
319
- def define_metadata_on_class(clazz)
320
- clazz.instance_variable_set :@ribs_metadata, Ribs::MetaData.new
321
- class << clazz
322
- include ClassMethods
323
- end
324
- clazz.class_eval do
325
- include InstanceMethods
326
- end
327
- end
328
292
  end
329
293
  end