glamping 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ # == About glamping/boot.rb (also known as <tt>boot.rb</tt>)
2
+ # If you don't like putting everthing into the same file and don't
3
+ # won't to cludder your file with many <tt>require</tt>'s, you should
4
+ # look at <tt>boot.rb</tt>.
5
+ #
6
+ # === Using
7
+ # require 'rubygems'
8
+ # require 'glamping/boot'
9
+ # Glamping.boot(binding)
10
+ #
11
+ # === Directory Structure
12
+ # app/
13
+ # controllers/
14
+ # helpers/
15
+ # models/
16
+ # views/
17
+ #
18
+ # public/
19
+ # cache/
20
+ # config/
21
+ # database.yml
22
+ # schema.rb
23
+ #
24
+ # sample_app.rb
25
+ #
26
+ # === Notes
27
+ # * <tt>boot.rb</tt> will automatically initialize a Glamping application
28
+ # based on the filename. So in this example, it will be named <tt>SampleApp</tt>
29
+ # * If you need databases, simply create the file <tt>config/database.yml</tt>:
30
+ # adapter: mysql
31
+ # host: localhost
32
+ # username: db_user
33
+ # password: db_pass
34
+ # database: sample_app
35
+ # * ActiveRecord won't be loaded at all unless <tt>config/database.yml</tt>
36
+ # is provided.
37
+ # * By adding <tt>sessions: true</tt> into <tt>config/database.yml</tt>,
38
+ # your application gets session support (See glamping/session.rb[link:files/lib/glamping/session_rb.html]).
39
+ # * The cache path will be set to <tt>public/cache/</tt> (See Glamping::Helpers::PageCaching)
40
+ # * The view path will be set to <tt>app/views/</tt>.
41
+ # * All files in <tt>public/</tt> will be served at /.
42
+ require 'glamping'
43
+ Glamping.class_eval do
44
+ BOOT = %q{
45
+ root = ENV["GLAMPING_ROOT"] || File.dirname(File.expand_path(__FILE__))
46
+ app_name = File.basename(__FILE__, ".rb").split('_').map{|word|word.capitalize}.join
47
+ Glamping.goes(app_name)
48
+ app = Object.const_get(app_name)
49
+ app.config.views = File.join(root,"app","views")
50
+ app.config.public = File.join(root, "public")
51
+ app.config.cache = File.join(app.config.public, "cache")
52
+
53
+ require 'glamping/db' if d=File.exists?(database_file = "config/database.yml")
54
+
55
+ (Dir["app/*/"] - ["app/views/"]).each do |mod_name|
56
+ mod = app.const_get(File.basename(mod_name).capitalize)
57
+ Dir[mod_name + "**/*.rb"].each do |file|
58
+ mod.module_eval File.read(file)
59
+ end
60
+ end
61
+
62
+ if d
63
+ require 'yaml'
64
+ f = YAML.load(File.read(database_file))
65
+ s = f.delete("session") || f.delete("sessions")
66
+ app::Models::Base.establish_connection(f)
67
+
68
+ if s
69
+ require 'glamping/session'
70
+ app.send(:include, Glamping::Session)
71
+ Glamping::Models::Session.create_schema
72
+ end
73
+ if File.exists?(a="config/schema.rb")
74
+ app::Models::AutoMigration.auto_migrate(File.read(a))
75
+ else
76
+ app::Models.create_schema
77
+ end
78
+ end
79
+ app.create if app.respond_to? :create
80
+ }
81
+
82
+ # A magically method which enables directory structure, connects
83
+ # to the database, running migrations and adds session-support.
84
+ #
85
+ # Should always be called with <tt>binding</tt>.
86
+ # Glamping.boot(binding)
87
+ def self.boot(b)
88
+ eval(BOOT,b)
89
+ end
90
+ end
@@ -0,0 +1,333 @@
1
+ class MissingLibrary < Exception # :nodoc:
2
+ end
3
+ begin
4
+ require 'active_record'
5
+ rescue LoadError => e
6
+ raise MissingLibrary, "ActiveRecord could not be loaded (is it installed?): #{e.message}"
7
+ end
8
+
9
+ $AR_EXTRAS = %q{
10
+ Base = ActiveRecord::Base unless const_defined? :Base
11
+
12
+ def Y; ActiveRecord::Base.verify_active_connections!; self; end
13
+
14
+ class SchemaInfo < Base
15
+ end
16
+
17
+ def self.V(n)
18
+ @final = [n, @final.to_i].max
19
+ m = (@migrations ||= [])
20
+ Class.new(ActiveRecord::Migration) do
21
+ meta_def(:version) { n }
22
+ meta_def(:inherited) { |k| m << k }
23
+ end
24
+ end
25
+
26
+ def self.create_schema(opts = {})
27
+ opts[:assume] ||= 0
28
+ opts[:version] ||= @final
29
+ if @migrations
30
+ unless SchemaInfo.table_exists?
31
+ ActiveRecord::Schema.define do
32
+ create_table SchemaInfo.table_name do |t|
33
+ t.column :version, :float
34
+ end
35
+ end
36
+ end
37
+
38
+ si = SchemaInfo.find(:first) || SchemaInfo.new(:version => opts[:assume])
39
+ if si.version < opts[:version]
40
+ @migrations.each do |k|
41
+ k.migrate(:up) if si.version < k.version and k.version <= opts[:version]
42
+ k.migrate(:down) if si.version > k.version and k.version > opts[:version]
43
+ end
44
+ si.update_attributes(:version => opts[:version])
45
+ end
46
+ end
47
+ end
48
+
49
+ module AutoMigration
50
+ def self.migrate(s=nil, &blk)
51
+ s=blk if block_given?
52
+ case s
53
+ when String
54
+ eval(s)
55
+ when File
56
+ eval(s.read)
57
+ when Proc
58
+ module_eval(&s)
59
+ end
60
+ end
61
+
62
+ class << self
63
+ alias :<< :migrate
64
+ end
65
+
66
+ @@tables_in_schema, @@indexes_in_schema = [], []
67
+
68
+ def self.auto_migrate(s=nil, &blk)
69
+ self.migrate(s, &blk)
70
+ self.cleanup
71
+ end
72
+
73
+ def self.cleanup
74
+ drop_unused_tables
75
+ drop_unused_indexes
76
+ end
77
+
78
+ def self.method_missing(method, *args, &block)
79
+ case method
80
+ when :create_table
81
+ auto_create_table(method, *args, &block)
82
+ when :add_index
83
+ auto_add_index(method, *args, &block)
84
+ else
85
+ method_missing_without_auto_migration(method, *args, &block)
86
+ end
87
+ end
88
+
89
+ def self.method_missing_without_auto_migration(method, *args, &block)
90
+ ActiveRecord::Migration.send(method, *args, &block)
91
+ end
92
+
93
+ def self.auto_create_table(method, *args, &block)
94
+ table_name = args.shift.to_s
95
+ options = args.pop || {}
96
+ (@@tables_in_schema ||= []) << table_name
97
+
98
+ # Table doesn't exist, create it
99
+ unless ActiveRecord::Base.connection.tables.include?(table_name)
100
+ return method_missing_without_auto_migration(method, *[table_name, options], &block)
101
+ end
102
+
103
+ # Grab database columns
104
+ fields_in_db = ActiveRecord::Base.connection.columns(table_name).inject({}) do |hash, column|
105
+ hash[column.name] = column
106
+ hash
107
+ end
108
+
109
+ # Grab schema columns (lifted from active_record/connection_adapters/abstract/schema_statements.rb)
110
+ table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(ActiveRecord::Base.connection)
111
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
112
+ yield table_definition
113
+ fields_in_schema = table_definition.columns.inject({}) do |hash, column|
114
+ hash[column.name.to_s] = column
115
+ hash
116
+ end
117
+
118
+ # Add fields to db new to schema
119
+ (fields_in_schema.keys - fields_in_db.keys).each do |field|
120
+ column = fields_in_schema[field]
121
+ add_column table_name, column.name, column.type.to_sym, :limit => column.limit, :precision => column.precision,
122
+ :scale => column.scale, :default => column.default, :null => column.null
123
+ end
124
+
125
+ # Remove fields from db no longer in schema
126
+ (fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
127
+ column = fields_in_db[field]
128
+ remove_column table_name, column.name
129
+ end
130
+
131
+ # Change field type if schema is different from db
132
+ (fields_in_schema.keys & fields_in_db.keys).each do |field|
133
+ if (field != 'id') && (fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym)
134
+ change_column table_name, fields_in_schema[field].name.to_sym, fields_in_schema[field].type.to_sym
135
+ end
136
+ end
137
+ end
138
+
139
+ def self.auto_add_index(method, *args, &block)
140
+ table_name = args.shift.to_s
141
+ fields = Array(args.shift).map(&:to_s)
142
+ options = args.shift
143
+
144
+ index_name = options[:name] if options
145
+ index_name ||= "index_#{table_name}_on_#{fields.join('_and_')}"
146
+
147
+ (@@indexes_in_schema ||= []) << index_name
148
+
149
+ unless ActiveRecord::Base.connection.indexes(table_name).detect { |i| i.name == index_name }
150
+ method_missing_without_auto_migration(method, *[table_name, fields, options], &block)
151
+ end
152
+ end
153
+
154
+ def self.drop_unused_tables
155
+ (ActiveRecord::Base.connection.tables - @@tables_in_schema - [SchemaInfo.table_name, "sessions"]).each do |table|
156
+ drop_table table
157
+ end
158
+ end
159
+
160
+ def self.drop_unused_indexes
161
+ @@tables_in_schema.each do |table_name|
162
+ indexes_in_db = ActiveRecord::Base.connection.indexes(table_name).map(&:name)
163
+ (indexes_in_db - @@indexes_in_schema & indexes_in_db).each do |index_name|
164
+ remove_index table_name, :name => index_name
165
+ end
166
+ end
167
+ end
168
+ end
169
+ }
170
+
171
+ module Glamping
172
+ # Models is an empty Ruby module for housing model classes derived
173
+ # from ActiveRecord::Base. As a shortcut, you may derive from Base
174
+ # which is an alias for ActiveRecord::Base.
175
+ #
176
+ # module Glamping::Models
177
+ # class Post < Base; belongs_to :user end
178
+ # class User < Base; has_many :posts end
179
+ # end
180
+ #
181
+ # == Where Models are Used
182
+ #
183
+ # Models are used in your controller classes. However, if your model class
184
+ # name conflicts with a controller class name, you will need to refer to it
185
+ # using the Models module.
186
+ #
187
+ # module Glamping::Controllers
188
+ # class Post < R '/post/(\d+)'
189
+ # def get(post_id)
190
+ # @post = Models::Post.find post_id
191
+ # render :index
192
+ # end
193
+ # end
194
+ # end
195
+ #
196
+ # == Regular Migration
197
+ # It's very easy to use migration in Glamping (and Camping). Just add
198
+ # a class in your <tt>Models</tt>. Remember that all tables begins with
199
+ # the name of your app.
200
+ #
201
+ # class InitialSchema < V 0.1
202
+ # def self.up
203
+ # create_table :sample_app_posts do |t|
204
+ # t.string :title
205
+ # t.text :content
206
+ # t.timestamps
207
+ # end
208
+ # end
209
+ #
210
+ # def self.down
211
+ # drop_table :sample_app_posts
212
+ # end
213
+ # end
214
+ #
215
+ # When you later need to add some more tables you just write another
216
+ # migration, but now increase the number to <tt>0.2</tt>.
217
+ #
218
+ # class AddComments < V 0.2
219
+ # def self.up
220
+ # create_table :sample_app_comments do |t|
221
+ # t.integer :post_id, :null => false
222
+ # t.string :author
223
+ # t.text :content
224
+ # end
225
+ # end
226
+ #
227
+ # def self.down
228
+ # drop_table :sample_app_comments
229
+ # end
230
+ # end
231
+ #
232
+ # === Running the migrations
233
+ # (Please read glamping/boot.rb[link:files/lib/glamping/boot_rb.html]
234
+ # when using <tt>boot.rb</tt>)
235
+ #
236
+ # In order to run the migrates, you call the <tt>create_schema</tt>
237
+ # method. It's a good idea to put this in the <tt>create</tt> method.
238
+ #
239
+ # def SampleApp.create
240
+ # SampleApp::Models.create_schema
241
+ # end
242
+ #
243
+ # == Auto Migration
244
+ # Auto Migration is a fine alternative to regular migrations. It was
245
+ # originally a plugin by PJ Hyett (link[http://errtheblog.com/posts/65-automatically]),
246
+ # and the version included here is 99% equal. Please note that there
247
+ # is impossible to rename a column when using auto migration.
248
+ #
249
+ # === Running the migration
250
+ # (Please read glamping/boot.rb[link:files/lib/glamping/boot_rb.html]
251
+ # when using <tt>boot.rb</tt>)
252
+ #
253
+ # A good idea is to put all your migration into one file, and then
254
+ # run <tt>auto_migration(File.open("schema.rb"))</tt>.
255
+ #
256
+ # # in schema.rb
257
+ # create_table Post.table_name do |t|
258
+ # t.string :title
259
+ # t.text :content
260
+ # t.timestamps
261
+ # end
262
+ #
263
+ # # in your main file
264
+ # def SampleApp.create
265
+ # SampleApp::Models::AutoMigration.auto_migrate(File.open("schema.rb"))
266
+ # end
267
+ #
268
+ # Whenever this file changes, it magically migrate everything for you.
269
+ # In this example I'm using <tt>Post.table_name</tt> in stead of
270
+ # <tt>:sample_app_posts</tt>, this is because the schema is loaded
271
+ # _after_ all the models.
272
+ #
273
+ # <tt>auto_migration</tt> is essentially only a <tt>migration</tt> and
274
+ # <tt>cleanup</tt> in the same method, so if you have migrations spread
275
+ # over several files do something like this:
276
+ #
277
+ # def SampleApp.create
278
+ # files = Dir["migrations/*.rb"]
279
+ # files.each do |f|
280
+ # SampleApp::Models::AutoMigration.migrate(File.open(f))
281
+ # end
282
+ # SampleApp::Models::AutoMigration.cleanup
283
+ # end
284
+ #
285
+ # === The Auto Migration License
286
+ # Copyright (c) 2007 PJ Hyett
287
+ #
288
+ # Permission is hereby granted, free of charge, to any person obtaining
289
+ # a copy of this software and associated documentation files (the
290
+ # "Software"), to deal in the Software without restriction, including
291
+ # without limitation the rights to use, copy, modify, merge, publish,
292
+ # distribute, sublicense, and/or sell copies of the Software, and to
293
+ # permit persons to whom the Software is furnished to do so, subject to
294
+ # the following conditions:
295
+ #
296
+ # The above copyright notice and this permission notice shall be
297
+ # included in all copies or substantial portions of the Software.
298
+ #
299
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
300
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
301
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
302
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
303
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
304
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
305
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
306
+
307
+ module Models
308
+ A = ActiveRecord
309
+ # Base is an alias for ActiveRecord::Base. The big warning I'm going to give you
310
+ # about this: *Base overloads table_name_prefix.* This means that if you have a
311
+ # model class Blog::Models::Post, it's table name will be <tt>blog_posts</tt>.
312
+ #
313
+ # ActiveRecord is not loaded if you never reference this class. The minute you
314
+ # use the ActiveRecord or Glamping::Models::Base class, then the ActiveRecord library
315
+ # is loaded.
316
+ Base = A::Base
317
+
318
+ # The default prefix for Glamping model classes is the topmost module name lowercase
319
+ # and followed with an underscore.
320
+ #
321
+ # Tepee::Models::Page.table_name_prefix
322
+ # #=> "tepee_pages"
323
+ #
324
+ def Base.table_name_prefix
325
+ "#{name[/\w+/]}_".downcase.sub(/^(#{A}|glamping)_/i,'')
326
+ end
327
+ module_eval $AR_EXTRAS
328
+ end
329
+ end
330
+ Glamping::S.sub! /def\s*Y[;\s]*self[;\s]*end/, $AR_EXTRAS
331
+ Object.constants.map{|c|Object.const_get(c)}.each do |c|
332
+ c::Models.module_eval $AR_EXTRAS if c.respond_to?(:goes)
333
+ end
@@ -0,0 +1,132 @@
1
+ # == About glamping/session.rb
2
+ #
3
+ # This file contains two modules which supply basic sessioning to your Glamping app.
4
+ # Again, we're dealing with a pretty little bit of code: approx. 60 lines.
5
+ #
6
+ # * Glamping::Models::Session is a module which adds a single <tt>sessions</tt> table
7
+ # to your database.
8
+ # * Glamping::Session is a module which you will mix into your application (or into
9
+ # specific controllers which require sessions) to supply a <tt>@state</tt> variable
10
+ # you can use in controllers and views.
11
+ #
12
+ # For a basic tutorial, see the *Getting Started* section of the Glamping::Session module.
13
+ 0 # Just a dummy-code for stupid RDOC...
14
+ module Glamping::Models
15
+ # A database table for storing Glamping sessions. Contains a unique 32-character hashid, a
16
+ # creation timestamp, and a column of serialized data called <tt>ivars</tt>.
17
+ class Session < Base
18
+ serialize :ivars
19
+ set_primary_key :hashid
20
+
21
+ def []=(k, v) # :nodoc:
22
+ self.ivars[k] = v
23
+ end
24
+ def [](k) # :nodoc:
25
+ self.ivars[k] rescue nil
26
+ end
27
+
28
+ protected
29
+ RAND_CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
30
+ def before_create
31
+ rand_max = RAND_CHARS.size
32
+ sid = (0...32).inject("") { |ret,_| ret << RAND_CHARS[rand(rand_max)] }
33
+ write_attribute('hashid', sid)
34
+ end
35
+
36
+ # Generates a new session ID and creates a row for the new session in the database.
37
+ def self.generate cookies
38
+ sess = Session.create :ivars => Glamping::H[]
39
+ cookies.camping_sid = sess.hashid
40
+ sess
41
+ end
42
+
43
+ # Gets the existing session based on the <tt>camping_sid</tt> available in cookies.
44
+ # If none is found, generates a new session.
45
+ def self.persist cookies
46
+ session = nil
47
+ if cookies.camping_sid
48
+ session = Glamping::Models::Session.find_by_hashid cookies.camping_sid
49
+ end
50
+ unless session
51
+ session = Glamping::Models::Session.generate cookies
52
+ end
53
+ session
54
+ end
55
+
56
+ # Builds the session table in the database. To be used in your application's
57
+ # <tt>create</tt> method.
58
+ #
59
+ # Like so:
60
+ #
61
+ # def Blog.create
62
+ # Glamping::Models::Session.create_schema
63
+ # unless Blog::Models::Post.table_exists?
64
+ # ActiveRecord::Schema.define(&Blog::Models.schema)
65
+ # end
66
+ # end
67
+ #
68
+ def self.create_schema
69
+ #unless table_exists?
70
+ ActiveRecord::Schema.define do
71
+ create_table :sessions, :force => true, :id => false do |t|
72
+ t.column :hashid, :string, :limit => 32, :null => false
73
+ t.column :created_at, :datetime
74
+ t.column :ivars, :text
75
+ end
76
+ add_index :sessions, [:hashid], :unique => true
77
+ end
78
+ reset_column_information
79
+ #end
80
+ end
81
+ end
82
+ end
83
+
84
+ module Glamping
85
+ # The Glamping::Session module is designed to be mixed into your application or into specific
86
+ # controllers which require sessions. This module defines a <tt>service</tt> method which
87
+ # intercepts all requests handed to those controllers.
88
+ #
89
+ # == Getting Started
90
+ # (Please read glamping/boot.rb[link:files/lib/glamping/boot_rb.html]
91
+ # when using <tt>boot.rb</tt>)
92
+ #
93
+ # To get sessions working for your application:
94
+ #
95
+ # 1. <tt>require 'camping/session'</tt>
96
+ # 2. Mixin the module: <tt>module YourApp; include Glamping::Session end</tt>
97
+ # 3. In your application's <tt>create</tt> method, add a call to <tt>Glamping::Models::Session.create_schema</tt>
98
+ # 4. Throughout your application, use the <tt>@state</tt> var like a hash to store your application's data.
99
+ #
100
+ # If you are unfamiliar with the <tt>create</tt> method, see
101
+ # http://code.whytheluckystiff.net/camping/wiki/GiveUsTheCreateMethod.
102
+ #
103
+ # == A Few Notes
104
+ #
105
+ # * The session ID is stored in a cookie. Look in <tt>@cookies.camping_sid</tt>.
106
+ # * The session data is stored in the <tt>sessions</tt> table in your database.
107
+ # * All mounted Glamping apps using this class will use the same database table.
108
+ # * However, your application's data is stored in its own hash.
109
+ # * Session data is only saved if it has changed.
110
+ # * Sessions are loaded from DB at *every* requests
111
+ module Session
112
+ # This <tt>service</tt> method, when mixed into controllers, intercepts requests
113
+ # and wraps them with code to start and close the session. If a session isn't found
114
+ # in the database it is created. The <tt>@state</tt> variable is set and if it changes,
115
+ # it is saved back into the database.
116
+ def service(*a)
117
+ session = Glamping::Models::Session.persist @cookies
118
+ app = self.class.name.gsub(/^(\w+)::.+$/, '\1')
119
+ @state = (session[app] ||= Glamping::H[])
120
+ hash_before = Marshal.dump(@state).hash
121
+ return super(*a)
122
+ ensure
123
+ if session
124
+ hash_after = Marshal.dump(@state).hash
125
+ unless hash_before == hash_after
126
+ session[app] = @state
127
+ session.save
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end