glamping 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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