studio54 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +31 -0
- data/Gemfile.lock +68 -0
- data/LICENSE +25 -0
- data/README.md +0 -0
- data/Rakefile +56 -0
- data/VERSION +5 -0
- data/app/controllers/users_controller.rb +29 -0
- data/app/models/post.rb +4 -0
- data/app/models/user.rb +46 -0
- data/bare/Gemfile +31 -0
- data/bare/Rakefile +28 -0
- data/bare/config.ru +9 -0
- data/bare/config/app_tie.rb +12 -0
- data/bare/config/db.rb +20 -0
- data/bare/config/db_connect.rb +19 -0
- data/bare/config/environment.rb +17 -0
- data/bare/config/sinatra.rb +36 -0
- data/bare/config/studio54_tie.rb +2 -0
- data/bare/dance.rb +11 -0
- data/bare/lib/after_filters.rb +7 -0
- data/bare/lib/before_filters.rb +7 -0
- data/bare/lib/helpers.rb +5 -0
- data/bare/public/index.rhtml +1 -0
- data/bare/public/layout.rhtml +12 -0
- data/bare/static/css/base.css +86 -0
- data/bare/static/css/yui_reset.css +30 -0
- data/bare/test/helpers.rb +8 -0
- data/bare/test/suite.rb +13 -0
- data/bin/studio54 +65 -0
- data/config.ru +9 -0
- data/config/app_tie.rb +8 -0
- data/config/db.rb +20 -0
- data/config/db_connect.rb +19 -0
- data/config/environment.rb +17 -0
- data/config/sinatra.rb +36 -0
- data/dance.rb +80 -0
- data/ideas +0 -0
- data/lib/after_filters.rb +7 -0
- data/lib/base.rb +29 -0
- data/lib/before_filters.rb +30 -0
- data/lib/helpers.rb +24 -0
- data/lib/lazy_controller.rb +87 -0
- data/lib/lazy_record.rb +395 -0
- data/lib/partials.rb +19 -0
- data/lib/studio54.rb +14 -0
- data/lib/vendor.rb +23 -0
- data/public/_partial_test.rhtml +4 -0
- data/public/all.rhtml +7 -0
- data/public/form.rhtml +11 -0
- data/public/index.rhtml +6 -0
- data/public/layout.rhtml +15 -0
- data/public/partial_test.rhtml +6 -0
- data/public/test_find_by.rhtml +4 -0
- data/rack/cache/body/ec/b48431757330e446c58d88e317574ef0eca2e9 +0 -0
- data/rack/cache/meta/25/3a7a342a66b79119b7b8cb34bda89978f9e606 +0 -0
- data/rack/cache/meta/c1/34c9b7112c7d884220d6ee9a8e43ec69d2ea6e +0 -0
- data/static/css/base.css +86 -0
- data/static/css/yui_reset.css +30 -0
- data/static/hello.html +1 -0
- data/studio54.gemspec +25 -0
- data/tags +149 -0
- data/test/email.rb +8 -0
- data/test/environment.rb +28 -0
- data/test/helpers.rb +8 -0
- data/test/integration/index_test.rb +41 -0
- data/test/integration/partial_test.rb +50 -0
- data/test/mail/email_test.rb +14 -0
- data/test/rack/helpers.rb +23 -0
- data/test/suite.rb +8 -0
- data/test/unit/associations_test.rb +33 -0
- data/test/unit/callbacks_test.rb +16 -0
- data/test/unit/database_test.rb +73 -0
- data/test/unit/model_introspection_test.rb +44 -0
- data/test/unit/serialization_test.rb +19 -0
- data/test/unit/validations_test.rb +73 -0
- metadata +165 -0
data/ideas
ADDED
File without changes
|
data/lib/base.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Studio54
|
2
|
+
class Base
|
3
|
+
include Config::Environment
|
4
|
+
# wrap Sinatra's scopes for convenience
|
5
|
+
# within the controller and model
|
6
|
+
cattr_accessor :app_class
|
7
|
+
cattr_accessor :app_instance
|
8
|
+
|
9
|
+
# All models are not required by default. These are helper
|
10
|
+
# methods to aid doing it manually
|
11
|
+
def self.require_models(*models)
|
12
|
+
models.each do |m|
|
13
|
+
require File.join(MODELSDIR, m.to_s)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
alias_method :require_model, :require_models
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.require_all_models
|
22
|
+
Dir.glob(File.join(MODELSDIR, '*')).each do |m_file|
|
23
|
+
require m_file
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Studio54
|
2
|
+
class Dancefloor
|
3
|
+
# wraps Dancefloor class scope (app) for convenience
|
4
|
+
Base.app_class = self
|
5
|
+
|
6
|
+
before do
|
7
|
+
load 'config/db_connect.rb' if self.class.environment ==
|
8
|
+
:development
|
9
|
+
# wraps Dancefloor instance scope (request) for convenience
|
10
|
+
Base.app_instance = self
|
11
|
+
|
12
|
+
# if using shotgun, create a custom log format
|
13
|
+
!settings.shotgun || begin
|
14
|
+
req = request
|
15
|
+
logger.class_eval do
|
16
|
+
cattr_accessor :format
|
17
|
+
self.__send__ :format=, <<-HTML
|
18
|
+
#{Time.now}
|
19
|
+
#{req.request_method} #{req.fullpath}
|
20
|
+
Content-Length: #{req.content_length}
|
21
|
+
Params:#{req.params}
|
22
|
+
HTML
|
23
|
+
end
|
24
|
+
logger.info logger.format
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
class Studio54::Dancefloor
|
2
|
+
helpers do
|
3
|
+
include Rack::Utils
|
4
|
+
# have to explicitly use h(...) to escape html
|
5
|
+
alias_method :h, :escape_html
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Sinatra
|
10
|
+
class Response
|
11
|
+
|
12
|
+
def set_content_length!
|
13
|
+
self["Content-Length"] =
|
14
|
+
self.body.inject(0) {|a, l| a += l.length}
|
15
|
+
end
|
16
|
+
|
17
|
+
def send(status=200)
|
18
|
+
set_content_length!
|
19
|
+
[status, self.headers, self.body]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class LazyController < Studio54::Base
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def inherited(base)
|
6
|
+
base.extend ActiveModel::Callbacks
|
7
|
+
end
|
8
|
+
|
9
|
+
def app_class_eval(&block)
|
10
|
+
self.app_class.class_eval &block
|
11
|
+
end
|
12
|
+
|
13
|
+
def app_instance_eval(&block)
|
14
|
+
self.app_instance.instance_eval &block
|
15
|
+
end
|
16
|
+
|
17
|
+
["before_action", "after_action", "around_action"].each do |m|
|
18
|
+
m =~ /\A(.*?)_/
|
19
|
+
type = $1
|
20
|
+
class_eval <<RUBY, __FILE__, __LINE__ + 1
|
21
|
+
def #{m}(action, &block)
|
22
|
+
define_callbacks ('old_' + action.to_s)
|
23
|
+
set_callback ('old_' + action.to_s), :#{type}, &block
|
24
|
+
define_singleton_method "method_added" do |action_name|
|
25
|
+
matchstring = Regexp.new('^' + action.to_s + '$')
|
26
|
+
if matchstring.match action_name
|
27
|
+
alias_method ('old_' + action.to_s).intern, action
|
28
|
+
remove_method action
|
29
|
+
define_method ("proxy_" + action.to_s) do |*args, &block|
|
30
|
+
run_callbacks ('old_' + action.to_s) do
|
31
|
+
__send__ ('old_' + action.to_s).intern , *args, &block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
define_method "method_missing" do |method, *args, &block|
|
35
|
+
if matchstring.match method
|
36
|
+
__send__ ("proxy_" + method.to_s), *args, &block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
RUBY
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# included in Dancefloor
|
47
|
+
module Routable
|
48
|
+
def self.included(base)
|
49
|
+
base.__send__ :use, Rack::Flash
|
50
|
+
base.__send__ :helpers, Sinatra::Partials
|
51
|
+
end
|
52
|
+
|
53
|
+
# Db is a constant in Studio54 scope
|
54
|
+
include ::Studio54
|
55
|
+
include ::Studio54::Config::Environment
|
56
|
+
|
57
|
+
def controller(c_name, c_action, params={})
|
58
|
+
require File.join(CONTROLLERSDIR, "#{c_name}_controller")
|
59
|
+
require File.join(MODELSDIR, c_name[0...-1])
|
60
|
+
begin
|
61
|
+
controller = self.class.const_get("#{c_name.capitalize}Controller")
|
62
|
+
controller_inst = controller.new
|
63
|
+
result = if params.blank?
|
64
|
+
controller_inst.__send__(c_action)
|
65
|
+
else
|
66
|
+
controller_inst.__send__(c_action, params)
|
67
|
+
end
|
68
|
+
controller_inst.instance_variables.each do |ivar|
|
69
|
+
# establish non block-local scope
|
70
|
+
ivar_value = nil
|
71
|
+
controller_inst.instance_eval do
|
72
|
+
ivar_value = instance_variable_get ivar
|
73
|
+
end
|
74
|
+
# self here is the instance of the application
|
75
|
+
instance_variable_set ivar, ivar_value
|
76
|
+
end
|
77
|
+
ensure
|
78
|
+
Db.conn.disconnect if Db.conn.kind_of? DBI::DatabaseHandle and
|
79
|
+
Db.conn.connected?
|
80
|
+
end
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
data/lib/lazy_record.rb
ADDED
@@ -0,0 +1,395 @@
|
|
1
|
+
class LazyRecord < Studio54::Base
|
2
|
+
include ::Studio54
|
3
|
+
# ActiveModel::Callbacks Base.class_eval {includes ActiveSupport::Callbacks}
|
4
|
+
extend ActiveModel::Callbacks
|
5
|
+
include ActiveModel::Serializers::JSON
|
6
|
+
include ActiveModel::Serializers::Xml
|
7
|
+
extend ActiveModel::Naming
|
8
|
+
extend ActiveModel::Translation
|
9
|
+
include ActiveModel::Validations
|
10
|
+
|
11
|
+
RecordNotFound = Class.new(StandardError)
|
12
|
+
AssociationNotFound = Class.new(StandardError)
|
13
|
+
|
14
|
+
def self.inherited(base)
|
15
|
+
base.class_eval do
|
16
|
+
cattr_accessor :primary_key
|
17
|
+
attr_reader :errors
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# if parameters are given, builds the model
|
22
|
+
# object with the attributes from the given
|
23
|
+
# parameters
|
24
|
+
def initialize(params=nil)
|
25
|
+
unless params.nil?
|
26
|
+
self.build_from_params!(params)
|
27
|
+
end
|
28
|
+
@errors = ActiveModel::Errors.new(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
# used to keep track of all table attributes
|
32
|
+
def self.tbl_attr_accessor *fields
|
33
|
+
fields.each do |f|
|
34
|
+
self.attributes << f.to_s unless self.attributes.include? f.to_s
|
35
|
+
end
|
36
|
+
self.__send__ :attr_accessor, *fields
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.attributes
|
40
|
+
@attributes ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.all_attributes
|
44
|
+
@all_attributes ||= begin
|
45
|
+
attrs = @attributes.dup
|
46
|
+
attrs.unshift primary_key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.nested_attributes
|
51
|
+
@nested_attributes ||= []
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.belongs_to_attributes
|
55
|
+
@belongs_to_attributes ||= []
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Defines the accessor(s) and makes sure the other model includes
|
61
|
+
# an appropriate association as well.
|
62
|
+
#
|
63
|
+
# The name of the model can be made explicit if it's different
|
64
|
+
# from the default (chop the trailing 's' off from the model name).
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
# has_many :tags, {:through => 'tags_articles', :fk => 'tagid'}
|
68
|
+
#
|
69
|
+
# default
|
70
|
+
# =======
|
71
|
+
#
|
72
|
+
# has_many :tags
|
73
|
+
#
|
74
|
+
# is equivalent to
|
75
|
+
#
|
76
|
+
# has_many :tags, {:through => 'tags_articles', :fk => 'tag_id'}
|
77
|
+
def self.has_many model, options={}
|
78
|
+
# @join_tables:
|
79
|
+
# Used with LR#build_associated, to find out what the join table and
|
80
|
+
# the foreign key are. If not found, it generates default ones by the
|
81
|
+
# heuristic explained above the LR#build_associated method declaration.
|
82
|
+
@join_tables ||= {}
|
83
|
+
unless @join_tables[model]
|
84
|
+
@join_tables[model] = {}
|
85
|
+
end
|
86
|
+
if through = options[:through]
|
87
|
+
@join_tables[model][:through] = through
|
88
|
+
end
|
89
|
+
if fk = options[:fk]
|
90
|
+
@join_tables[model][:fk] = fk
|
91
|
+
end
|
92
|
+
|
93
|
+
model_string = model.to_s.singularize
|
94
|
+
self.require_model model_string
|
95
|
+
model_klass = Object.const_get model_string.camelize
|
96
|
+
ivar_name = model
|
97
|
+
|
98
|
+
# Make sure associated model #belongs_to this class.
|
99
|
+
unless model_klass.belongs_to_attributes.include? self.name.
|
100
|
+
tableize
|
101
|
+
raise AssociationNotFound.new "#{model_klass} doesn't #belong_to " \
|
102
|
+
"#{self.name}"
|
103
|
+
end
|
104
|
+
class_eval do
|
105
|
+
define_method ivar_name do
|
106
|
+
instance_variable_get("@#{ivar_name}") || []
|
107
|
+
end
|
108
|
+
attr_writer ivar_name
|
109
|
+
self.nested_attributes << ivar_name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class << self
|
114
|
+
# has_one has the same implementation as has_many
|
115
|
+
alias_method :has_one, :has_many
|
116
|
+
end
|
117
|
+
|
118
|
+
# barely does anything, just works with has_many
|
119
|
+
def self.belongs_to *models
|
120
|
+
models.each do |m|
|
121
|
+
self.belongs_to_attributes << m.to_s
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.attr_primary(*fields)
|
126
|
+
if fields.length == 1
|
127
|
+
self.primary_key = fields[0].to_s != "" ? fields[0].to_s : nil
|
128
|
+
else
|
129
|
+
self.primary_key = fields.map {|f| f.to_s }
|
130
|
+
end
|
131
|
+
class_eval do
|
132
|
+
fields.each do |f|
|
133
|
+
attr_accessor f unless f.nil?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# associated table name, by default is just to add an 's' to the model
|
139
|
+
# name
|
140
|
+
def self.assoc_table_name=( tblname=self.name.tableize )
|
141
|
+
self.__send__ :cattr_accessor, :table_name
|
142
|
+
self.table_name = tblname.to_s
|
143
|
+
end
|
144
|
+
|
145
|
+
public
|
146
|
+
|
147
|
+
# meant for internal use
|
148
|
+
def build_from_params!(params)
|
149
|
+
params.each do |k, v|
|
150
|
+
self.__send__("#{k}=".intern, v)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Have to implement Model#attributes to play nice
|
155
|
+
# with ActiveModel serializers
|
156
|
+
def attributes(options={})
|
157
|
+
opts = {:include_pk => true}.merge options
|
158
|
+
if opts[:include_pk]
|
159
|
+
attrs = self.class.all_attributes
|
160
|
+
else
|
161
|
+
attrs = self.class.attributes
|
162
|
+
end
|
163
|
+
{}.tap do |h|
|
164
|
+
attrs.each do |a_name|
|
165
|
+
h[a_name] = instance_variable_get "@#{a_name}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# save current model instance into database
|
171
|
+
def save
|
172
|
+
return unless valid?
|
173
|
+
sql = "INSERT INTO #{self.class.table_name} ("
|
174
|
+
fields = self.class.attributes
|
175
|
+
sql += fields.join(', ') + ') VALUES ('
|
176
|
+
fields.each {|f| sql += '?, '}
|
177
|
+
sql = sql[0...-2] + ')'
|
178
|
+
values = fields.map do |f|
|
179
|
+
ivar = instance_variable_get "@#{f}"
|
180
|
+
if ivar.nil?
|
181
|
+
"NULL"
|
182
|
+
else
|
183
|
+
ivar
|
184
|
+
end
|
185
|
+
end
|
186
|
+
result = nil
|
187
|
+
self.class.db_try do
|
188
|
+
result = Db.conn.execute sql, *values
|
189
|
+
end
|
190
|
+
result ? true : false
|
191
|
+
end
|
192
|
+
|
193
|
+
# delete the current model instance from the database
|
194
|
+
def destroy(options={})
|
195
|
+
if options[:where]
|
196
|
+
else
|
197
|
+
sql = "DELETE FROM #{self.class.table_name} WHERE " \
|
198
|
+
"#{self.primary_key} = ?"
|
199
|
+
result = nil
|
200
|
+
self.class.db_try do
|
201
|
+
result = Db.conn.execute sql,
|
202
|
+
instance_variable_get("@#{self.primary_key}")
|
203
|
+
end
|
204
|
+
result
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# update the current model instance in the database
|
209
|
+
def update_attributes(params, extra_where={})
|
210
|
+
values = []
|
211
|
+
key = self.primary_key
|
212
|
+
id = params.delete key
|
213
|
+
if extra_where.blank?
|
214
|
+
sql = "UPDATE #{self.class.table_name} SET "
|
215
|
+
params.each do |k,v|
|
216
|
+
sql += "#{k} = ?, "
|
217
|
+
values << v
|
218
|
+
end
|
219
|
+
sql = sql[0...-2]
|
220
|
+
sql += " WHERE #{key} = ?"
|
221
|
+
values << id
|
222
|
+
else
|
223
|
+
end
|
224
|
+
res = nil
|
225
|
+
self.class.db_try do
|
226
|
+
res = Db.conn.execute sql, *values
|
227
|
+
end
|
228
|
+
res
|
229
|
+
end
|
230
|
+
|
231
|
+
# This method uses the model instance's class::has_many()
|
232
|
+
# method to determine what the join table is called. The
|
233
|
+
# default join table name that this method uses if no
|
234
|
+
# options were given to Model::has_many() (see Model::has_many()
|
235
|
+
# for the passable options) is the following:
|
236
|
+
#
|
237
|
+
# the tbl argument (for example, :tags), concatenated with
|
238
|
+
# `self`'s class's table name (for example, 'articles')
|
239
|
+
# to make 'tags_articles'
|
240
|
+
#
|
241
|
+
# The default foreign key uses a similar heuristic. For the
|
242
|
+
# example above, it would be 'tag_id', because the given
|
243
|
+
# table name is 'tags', and the singular is 'tag'. This
|
244
|
+
# is then concatenated with '_id'
|
245
|
+
#
|
246
|
+
# To override the defaults, provide options to Model::has_many()
|
247
|
+
def build_associated tbl
|
248
|
+
self_tbl = self.class.table_name
|
249
|
+
through = if _through = self.class.
|
250
|
+
instance_variable_get("@join_tables")[tbl][:through]
|
251
|
+
_through
|
252
|
+
else
|
253
|
+
"#{tbl}_#{self_tbl}"
|
254
|
+
end
|
255
|
+
fk = if _fk = self.class.
|
256
|
+
instance_variable_get("@join_tables")[tbl][:fk]
|
257
|
+
_fk
|
258
|
+
else
|
259
|
+
"#{tbl.to_s.singularize}_id"
|
260
|
+
end
|
261
|
+
|
262
|
+
tbl_model_name = tbl.to_s.singularize.camelize
|
263
|
+
begin
|
264
|
+
tbl_model = Object.const_get tbl_model_name
|
265
|
+
rescue NameError
|
266
|
+
retry if require "app/models/#{tbl_model_name.downcase}"
|
267
|
+
end
|
268
|
+
pk = self.class.primary_key
|
269
|
+
id = __send__ pk
|
270
|
+
sql = "SELECT * FROM #{tbl} INNER JOIN #{through} ON #{tbl}.#{tbl_model.primary_key} = " \
|
271
|
+
"#{through}.#{fk} WHERE #{through}.#{self_tbl.singularize + '_id'} = ?"
|
272
|
+
puts sql
|
273
|
+
res = nil
|
274
|
+
self.class.db_try do
|
275
|
+
res = Db.conn.execute sql, id
|
276
|
+
end
|
277
|
+
objs = tbl_model.build_from res
|
278
|
+
p objs
|
279
|
+
objs = Array.wrap(objs) unless Array === objs
|
280
|
+
__send__("#{tbl}=", __send__(tbl) + objs) unless objs.blank?
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.db_try
|
284
|
+
begin
|
285
|
+
yield
|
286
|
+
rescue DBI::DatabaseError
|
287
|
+
@retries ||= 0; @retries += 1
|
288
|
+
if @retries == 1
|
289
|
+
load 'config/db_connect.rb'
|
290
|
+
retry
|
291
|
+
else
|
292
|
+
raise
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# id is the primary key of the table, and does
|
298
|
+
# not need to be named 'id' in the table itself
|
299
|
+
# TODO take into account other dbms's, this only
|
300
|
+
# works w/ mysql
|
301
|
+
def self.find(id)
|
302
|
+
sql = "SELECT * FROM #{self.table_name} WHERE #{self.primary_key} = ?"
|
303
|
+
res = nil
|
304
|
+
db_try do
|
305
|
+
res = Db.conn.execute sql, id
|
306
|
+
end
|
307
|
+
build_from res
|
308
|
+
end
|
309
|
+
|
310
|
+
def self.find_by(hash, options={})
|
311
|
+
opts = {:conjunction => 'AND'}.merge options
|
312
|
+
conj = opts[:conjunction]
|
313
|
+
sql = "SELECT * FROM #{self.table_name} WHERE "
|
314
|
+
values = []
|
315
|
+
hash.each do |k, v|
|
316
|
+
sql += "#{k} = ? #{conj} "
|
317
|
+
values << v
|
318
|
+
end
|
319
|
+
case conj
|
320
|
+
when 'AND'
|
321
|
+
sql = sql[0...-4]
|
322
|
+
when 'OR'
|
323
|
+
sql = sql[0...-3]
|
324
|
+
else
|
325
|
+
raise "conjunction in sql condition (WHERE) must be one of AND, OR"
|
326
|
+
end
|
327
|
+
res = nil
|
328
|
+
db_try do
|
329
|
+
res = Db.conn.execute sql, *values
|
330
|
+
end
|
331
|
+
build_from res
|
332
|
+
end
|
333
|
+
|
334
|
+
def self.all
|
335
|
+
sql = "SELECT * FROM #{self.table_name}"
|
336
|
+
res = nil
|
337
|
+
db_try do
|
338
|
+
res = Db.conn.execute(sql)
|
339
|
+
end
|
340
|
+
build_from res, :always_return_array => true
|
341
|
+
end
|
342
|
+
|
343
|
+
private
|
344
|
+
|
345
|
+
# meant for internal use
|
346
|
+
def self.build_from(resultset, options={})
|
347
|
+
opts = {:always_return_array => false}.merge options
|
348
|
+
test_resultset resultset
|
349
|
+
model_instances = [].tap do |m|
|
350
|
+
resultset.fetch_hash do |h|
|
351
|
+
model = self.new
|
352
|
+
model.build_from_params! h
|
353
|
+
m << model
|
354
|
+
end
|
355
|
+
resultset.finish
|
356
|
+
end
|
357
|
+
model_instances.length == 1 && !opts[:always_return_array] ?
|
358
|
+
model_instances[0] : model_instances
|
359
|
+
end
|
360
|
+
|
361
|
+
# meant for internal use
|
362
|
+
def self.test_resultset(res)
|
363
|
+
if res.blank?
|
364
|
+
raise RecordNotFound.new "Bad resultset #{res}"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
public
|
369
|
+
|
370
|
+
class << self
|
371
|
+
def method_missing(method, *args, &block)
|
372
|
+
if method =~ %r{find_by_(.*)}
|
373
|
+
h_args = {$1 => args[0]}
|
374
|
+
return __send__ :find_by, h_args, &block
|
375
|
+
end
|
376
|
+
|
377
|
+
if method =~ %r{table_name}
|
378
|
+
return __send__ :assoc_table_name=
|
379
|
+
end
|
380
|
+
|
381
|
+
super
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
# Arrays should respond to build_associated just like models.
|
388
|
+
class Array
|
389
|
+
def build_associated tbl
|
390
|
+
each do |m|
|
391
|
+
m.__send__ :build_associated, tbl
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|