Class: Rack::Scaffold

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/scaffold.rb,
lib/rack/scaffold/adapters.rb

Defined Under Namespace

Modules: Adapters

Constant Summary collapse

ACTIONS =
[:subscribe, :create, :read, :update, :destroy]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Scaffold

Returns a new instance of Scaffold.

Raises:

  • (ArgumentError)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rack/scaffold.rb', line 15

def initialize(options = {})
  raise ArgumentError, "Missing option: :model or :models" unless options[:model] or options[:models]

  if options[:models] and options[:models].kind_of?(Array)
    @app = Rack::Cascade.new(options.delete(:models).collect{|model| self.class.new(options.dup.merge({model: model}))}) and return
  end

  @app = Class.new(Sinatra::Base) do
    use Rack::PostBodyContentTypeParser
    register Sinatra::MultiRoute
    helpers Sinatra::Param

    before do
      content_type :json
    end

    disable :raise_errors, :show_exceptions

    def last_modified_time(resource, resources)
      update_timestamp_field = resource.update_timestamp_field.to_sym
      most_recently_updated = resources.class.include?(Enumerable) ? resources.max_by(&update_timestamp_field) : resources

      timestamp = request.env['HTTP_IF_MODIFIED_SINCE']
      timestamp = most_recently_updated.send(update_timestamp_field) if most_recently_updated
      timestamp
    end

    def notify!(record)
      return unless @@connections
      
      pathname = Pathname.new(request.path)

      lines = []
      lines << "event: patch"

      op = case status
           when 201 then :add
           when 204 then :remove
           else
             :update
           end

      data = [{op: op, path: record.url, value: record}].to_json

      @@connections[pathname.dirname].each do |out|
        out << "event: patch\ndata: #{data}\n\n"
      end
    end
  end

  @actions = (options[:only] || ACTIONS) - (options[:except] || [])

  @adapter = Rack::Scaffold.adapters.detect{|adapter| adapter === options[:model]}
  raise "No suitable adapters found for #{options[:model]} in #{Rack::Scaffold.adapters}" unless @adapter

  resources = Array(@adapter.resources(options[:model], options))
  resources.each do |resource|
    @app.instance_eval do
      @@connections = Hash.new([])

      route :get, :subscribe, "/#{resource.plural}/?" do
        pass unless request.accept? 'text/event-stream'

        content_type 'text/event-stream'

        stream :keep_open do |out|
          @@connections[request.path] << out

          out.callback do
            @@connections[request.path].delete(out)
          end
        end
      end
    end if @actions.include?(:subscribe)

    @app.instance_eval do
      post "/#{resource.plural}/?" do
        if record = resource.create!(params)
          status 201
          notify!(record)
          {"#{resource.singular}" => record}.to_json
        else
          status 406
          {errors: record.errors}.to_json
        end
      end
    end if @actions.include?(:create)

    @app.instance_eval do
      get "/#{resource.plural}/?" do
        if params[:page] or params[:per_page]
          param :page, Integer, default: 1, min: 1
          param :per_page, Integer, default: 300, in: (1..300)

          resources = resource.paginate(params[:per_page], (params[:page] - 1) * params[:per_page])
          last_modified(last_modified_time(resource, resources)) if resource.timestamps?

          {
            "#{resource.plural}" => resources,
            page: params[:page],
            total: resource.count
          }.to_json
        else
          param :limit, Integer, default: 300, in: (1..300)
          param :offset, Integer, default: 0, min: 0

          resources = resource.paginate(params[:limit], params[:offset])
          last_modified(last_modified_time(resource, resources)) if resource.timestamps?

          {
            "#{resource.plural}" => resources
          }.to_json
        end
      end

      get "/#{resource.plural}/:id/?" do
        record = resource[params[:id]] or halt 404
        last_modified(last_modified_time(resource, record)) if resource.timestamps?
        {"#{resource.singular}" => record}.to_json
      end

      resource.one_to_many_associations.each do |association|
        get "/#{resource.plural}/:id/#{association}/?" do
          record = resource[params[:id]] or halt 404
          associations = record.send(association)

          {
            "#{association}" => associations
          }.to_json
        end
      end          
    end if @actions.include?(:read)

    @app.instance_eval do
      route :put, :patch, "/#{resource.plural}/:id/?" do
        record = resource[params[:id]] or halt 404
        if record.update!(params)
          status 200
          notify!(record)
          {"#{resource.singular}" => record}.to_json
        else
          status 406
          {errors: record.errors}.to_json
        end
      end
    end if @actions.include?(:update)

    @app.instance_eval do
      delete "/#{resource.plural}/:id/?" do
        record = resource[params[:id]] or halt 404
        if record.destroy
          status 204
          notify!(record)
        else
          status 406
          {errors: record.errors}.to_json
        end
      end
    end if @actions.include?(:destroy)
  end
end

Class Method Details

.adaptersObject



3
4
5
# File 'lib/rack/scaffold/adapters.rb', line 3

def self.adapters
  @@adapters ||= []
end

Instance Method Details

#call(env) ⇒ Object



177
178
179
# File 'lib/rack/scaffold.rb', line 177

def call(env)
  @app.call(env)
end