studio54 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/Gemfile +31 -0
  2. data/Gemfile.lock +68 -0
  3. data/LICENSE +25 -0
  4. data/README.md +0 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +5 -0
  7. data/app/controllers/users_controller.rb +29 -0
  8. data/app/models/post.rb +4 -0
  9. data/app/models/user.rb +46 -0
  10. data/bare/Gemfile +31 -0
  11. data/bare/Rakefile +28 -0
  12. data/bare/config.ru +9 -0
  13. data/bare/config/app_tie.rb +12 -0
  14. data/bare/config/db.rb +20 -0
  15. data/bare/config/db_connect.rb +19 -0
  16. data/bare/config/environment.rb +17 -0
  17. data/bare/config/sinatra.rb +36 -0
  18. data/bare/config/studio54_tie.rb +2 -0
  19. data/bare/dance.rb +11 -0
  20. data/bare/lib/after_filters.rb +7 -0
  21. data/bare/lib/before_filters.rb +7 -0
  22. data/bare/lib/helpers.rb +5 -0
  23. data/bare/public/index.rhtml +1 -0
  24. data/bare/public/layout.rhtml +12 -0
  25. data/bare/static/css/base.css +86 -0
  26. data/bare/static/css/yui_reset.css +30 -0
  27. data/bare/test/helpers.rb +8 -0
  28. data/bare/test/suite.rb +13 -0
  29. data/bin/studio54 +65 -0
  30. data/config.ru +9 -0
  31. data/config/app_tie.rb +8 -0
  32. data/config/db.rb +20 -0
  33. data/config/db_connect.rb +19 -0
  34. data/config/environment.rb +17 -0
  35. data/config/sinatra.rb +36 -0
  36. data/dance.rb +80 -0
  37. data/ideas +0 -0
  38. data/lib/after_filters.rb +7 -0
  39. data/lib/base.rb +29 -0
  40. data/lib/before_filters.rb +30 -0
  41. data/lib/helpers.rb +24 -0
  42. data/lib/lazy_controller.rb +87 -0
  43. data/lib/lazy_record.rb +395 -0
  44. data/lib/partials.rb +19 -0
  45. data/lib/studio54.rb +14 -0
  46. data/lib/vendor.rb +23 -0
  47. data/public/_partial_test.rhtml +4 -0
  48. data/public/all.rhtml +7 -0
  49. data/public/form.rhtml +11 -0
  50. data/public/index.rhtml +6 -0
  51. data/public/layout.rhtml +15 -0
  52. data/public/partial_test.rhtml +6 -0
  53. data/public/test_find_by.rhtml +4 -0
  54. data/rack/cache/body/ec/b48431757330e446c58d88e317574ef0eca2e9 +0 -0
  55. data/rack/cache/meta/25/3a7a342a66b79119b7b8cb34bda89978f9e606 +0 -0
  56. data/rack/cache/meta/c1/34c9b7112c7d884220d6ee9a8e43ec69d2ea6e +0 -0
  57. data/static/css/base.css +86 -0
  58. data/static/css/yui_reset.css +30 -0
  59. data/static/hello.html +1 -0
  60. data/studio54.gemspec +25 -0
  61. data/tags +149 -0
  62. data/test/email.rb +8 -0
  63. data/test/environment.rb +28 -0
  64. data/test/helpers.rb +8 -0
  65. data/test/integration/index_test.rb +41 -0
  66. data/test/integration/partial_test.rb +50 -0
  67. data/test/mail/email_test.rb +14 -0
  68. data/test/rack/helpers.rb +23 -0
  69. data/test/suite.rb +8 -0
  70. data/test/unit/associations_test.rb +33 -0
  71. data/test/unit/callbacks_test.rb +16 -0
  72. data/test/unit/database_test.rb +73 -0
  73. data/test/unit/model_introspection_test.rb +44 -0
  74. data/test/unit/serialization_test.rb +19 -0
  75. data/test/unit/validations_test.rb +73 -0
  76. metadata +165 -0
data/ideas ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ module Studio54
2
+ class Dancefloor
3
+ after do
4
+ end
5
+ end
6
+ end
7
+
@@ -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
+
@@ -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
+
@@ -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
+