Class: E

Inherits:
Object
  • Object
show all
Includes:
EL::ContentHelpers, EL::TagFactory
Defined in:
lib/el/crud.rb,
lib/el/cache.rb,
lib/el/assets.rb,
lib/el/tag_factory.rb,
lib/el/content_helpers.rb

Constant Summary collapse

E__CRUD__AUTH_TEST_PAYLOAD_KEY =
"E__CRUD__AUTH_TEST_PAYLOAD_#{rand(2**64)}".freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from EL::ContentHelpers

#capture_html, #content_for, #content_for?, #yield_content

Methods included from EL::TagFactory

#comment_tag, #comment_tag!, #css_tag, #doctype_tag, #js_tag

Class Method Details

.crudify(resource, *path_or_opts, &proc) ⇒ Object Also known as: crud

automatically creates POST/PUT/DELETE actions and map them to corresponding methods of given resource, so sending a POST request to the controller resulting in creating new object of given resource. PUT/PATCH requests will update objects by given id. DELETE requests will delete objects by given id.

Parameters:

  • path_or_opts (Hash)

    a customizable set of options

Options Hash (*path_or_opts):

  • :exclude (String || Array)

    sometimes forms sending extra params. exclude them using :exclude option

  • :pkey (Integer)

    item’s primary key(default :id) to be returned when item created /updated. if no pkey given and item does not respond to :id, the item itself returned.

  • :halt_on_errors (Integer)

    if created/updated item contain errors, halt processing unconditionally, even if proc given. if this option is false(default) and a proc given, it will pass item object and extracted errors to proc rather than halt.

  • :halt_with (Integer)

    when resource are not created/updated because of errors, Espresso will halt operation with 500 error status code. use :halt_with option to set a custom error status code.

  • :join_with (String)

    if resource thrown some errors, Espresso will join them using a coma. use :join_with option to set a custom glue.



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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/el/crud.rb', line 32

def crudify resource, *path_or_opts, &proc
  opts = path_or_opts.last.is_a?(Hash) ? path_or_opts.pop : {}
  if opts[:exclude]
    opts[:exclude] = [ opts[:exclude] ] unless opts[:exclude].is_a?(Array)
  else
    opts[:exclude] = []
  end
  path = path_or_opts.first
  action = '%s_' << (path || :index).to_s
  orm = :ar if resource.respond_to?(:arel_table)
  orm = :dm if resource.respond_to?(:storage_name)
  orm = :sq if resource.respond_to?(:db_schema)
  orm_map = {
    ar: {
        get: :find,
        put: :update_attributes,
      patch: :update_attributes
    },
    sq: {
      get: :[]
    }
  }[orm] || {}
  resource_method = {
       get: opts.fetch(:get,    orm_map[:get] || :get),
       put: opts.fetch(:put,    orm_map[:put] || :update),
      post: opts.fetch(:post,   :create),
     patch: opts.fetch(:patch,  orm_map[:patch] || :update),
    delete: opts.fetch(:delete, :destroy),
  }
  
  proc_accept_object, proc_accept_errors = nil
  if proc
    proc_accept_object = proc.arity > 0
    proc_accept_errors = proc.arity > 1
  end

  pkey      = opts[:pkey]      || :id
  join_with = opts[:join_with] || ', '
  halt_with = opts[:halt_with] || 500

  presenter = lambda do |controller_instance, obj, err|

    # extracting errors, if any
    errors = nil
    if err || (obj.respond_to?(:errors) && (err = obj.errors) &&
      err.respond_to?(:size) && err.size > 0)

      if err.respond_to?(:join)
        errors = err
      else
        if err.respond_to?(:each_pair) # seems error is a Hash
          # some objects may respond to `each_pair` but not respond to `inject`,
          # so using trivial looping to extract error messages.
          errors = []
          err.each_pair do |k,v|
            # usually DataMapper returns errors in the following format:
            # { :property => ['error 1', 'error 2'] }
            # flatten is here just in case we get nested arrays.
            error = v.is_a?(Array) ? v.flatten.join(join_with) : v.to_s
            errors << '%s: %s' % [k, error]
          end
        elsif err.respond_to?(:to_a) # not Array nor Hash, but convertible to Array
          # converting error to Array and joining
          errors = err.to_a
        elsif err.is_a?(String)
          errors = [err]
        else
          errors = [err.inspect]
        end
      end
    end
    errors && errors = controller_instance.escape_html(errors.join(join_with))
    
    if proc
      if errors && opts[:halt_on_errors]
        controller_instance.halt halt_with, errors
      end
      proc_args = []
      proc_args = [obj] if proc_accept_object
      proc_args = [obj, errors] if proc_accept_errors
      controller_instance.instance_exec(*proc_args, &proc)
    else
      if errors
        # no proc given, so halting when some errors occurring
        controller_instance.styled_halt halt_with, errors
      else
        # no proc given and no errors detected,
        # so extracting and returning object's pkey.
        # if no pkey given and object does not respond to :id nor to :[],
        # returning object as is.
        if obj.respond_to?(:[]) && obj.respond_to?(:has_key?)
          obj.has_key?(pkey) ? obj[pkey] : obj
        elsif obj.respond_to?(pkey)
          obj.send(pkey)
        else
          obj
        end
      end
    end
  end
  
  fetch_object = lambda do |controller_instance, id|
    obj, err = nil
    begin
      id = id.to_i if id =~ /\A\d+\Z/
      obj = resource.send(resource_method[:get], id) ||
        controller_instance.halt(404, 'object with ID %s not found' % controller_instance.escape_html(id))
    rescue => e
      err = e.message
    end
    [obj, err]
  end

  create_object = lambda do |controller_instance|
    obj, err = nil
    begin
      data = controller_instance.params.reject { |k,v| opts[:exclude].include?(k) }
      unless data.has_key?(E__CRUD__AUTH_TEST_PAYLOAD_KEY)
        obj = resource.send(resource_method[:post], data)
      end
    rescue => e
      err = e.message
    end
    [obj, err]
  end

  update_object = lambda do |controller_instance, request_method, id|
    obj, err = nil
    begin
      obj, err = fetch_object.call(controller_instance, id)
      unless err
        data = controller_instance.params.reject { |k,v| opts[:exclude].include?(k) }
        obj.send(resource_method[request_method], data)
      end
    rescue => e
      err = e.message
    end
    [obj, err]
  end

  # fetching object by given id
  # and calling #destroy(or whatever in options for delete) on it
  #
  # @return [String] empty string
  delete_object = lambda do |id|
    err = nil
    begin
      obj, err = fetch_object.call(self, id)
      unless err
        meth = resource_method[:delete]
        if obj.respond_to?(meth)
           obj.send(meth)
        else
          err = '%s does not respond to %s' % [obj.inspect, escape_html(meth)]
        end
      end
    rescue => e
      err = e.message
    end
    [nil, err]
  end

  options = lambda do |controller_instance|
    EConstants::HTTP__REQUEST_METHODS.reject do |rm|
      next if rm == 'OPTIONS'
      args  = rm == 'POST' ? 
        [{E__CRUD__AUTH_TEST_PAYLOAD_KEY => 'true'}] : 
        [E__CRUD__AUTH_TEST_PAYLOAD_KEY]
      s,h,b = controller_instance.invoke(action % rm.downcase, *args) do |env|
        env.update EConstants::ENV__REQUEST_METHOD => rm
      end
      s == EConstants::STATUS__PROTECTED
    end.join(', ')
  end

  self.class_exec do

    define_method action % :get do |id|
      presenter.call self, *fetch_object.call(self, id)
    end

    define_method action % :head do |id|
      presenter.call self, *fetch_object.call(self, id)
    end

    define_method action % :post do
      presenter.call self, *create_object.call(self)
    end

    [:put, :patch].each do |request_method|
      define_method action % request_method do |id|
        presenter.call self, *update_object.call(self, request_method, id)
      end
    end

    define_method action % :delete do |id|
      presenter.call self, *delete_object.call(id)
    end

    define_method action % :options do
      options.call self
    end
  end

end

Instance Method Details

#assets(*args, &proc) ⇒ Object



73
74
75
# File 'lib/el/assets.rb', line 73

def assets *args, &proc
  app.assets *args, &proc
end

#assets_mapper(*args, &proc) ⇒ Object



77
78
79
# File 'lib/el/assets.rb', line 77

def assets_mapper *args, &proc
  EL::AssetsMapper.new *args, &proc
end

#cache(*a, &b) ⇒ Object



2
# File 'lib/el/cache.rb', line 2

def cache(*a, &b);    app.cache(*a, &b);    end

#cache_poolObject



4
# File 'lib/el/cache.rb', line 4

def cache_pool;       app.cache_pool;       end

#clear_cache!(*a) ⇒ Object



3
# File 'lib/el/cache.rb', line 3

def clear_cache!(*a); app.clear_cache!(*a); end
Note:

the anchor will be HTML escaped

build a HTML <a> tag.

if first param is a valid action, the URL of given action will be used. action accepted as a symbol or a string representing action name and format. action can also be passed in deRESTified form, eg. :read instead of :post_read

if nil passed as first argument, a void link will be created

anchor can be passed via second argument. if it is missing, the link will be used as anchor anchor can also be passed as a block

attributes can be passed as a hash via last argument

Examples:


class App < E
  format '.html'

  def read
    link_to :read        #=> /app/read
    link_to 'read.html'  #=> /app/read.html
    link_to 'read.xml'   #=> read.xml - not translated, used as is
  end

  def post_write
    link_to :post_write  #=> /app/write - works but it is tedious, use :write instead
    link_to :write       #=> /app/write
    link_to 'write.html' #=> /app/write.html
    link_to '/something' #=> /something - not translated, used as is
  end
end

link_to nil, 'something' #=> <a href="javascript:void(null);">something</a>

link_to :something   #=> <a href="/something">/something</a>
link_to :foo, 'bar'  #=> <a href="/foo">bar</a>

link_to(:foo) { 'bar' }  #=> <a href="/foo">bar</a>

link_to :foo, target: '_blank'        #=> <a href="/foo" target="_blank">/foo</a>
link_to :foo, :bar, target: '_blank'  #=> <a href="/foo" target="_blank">bar</a>


170
171
172
173
# File 'lib/el/tag_factory.rb', line 170

def link_to *args, &proc
  '<a href="%s"%s>%s</a>' %
    EUtils.requisites_for_link_to(true, self.class, *args, &proc)
end

same as link_to except it wont escape the anchor



176
177
178
179
# File 'lib/el/tag_factory.rb', line 176

def link_to! *args, &proc
  '<a href="%s"%s>%s</a>' %
    EUtils.requisites_for_link_to(false, self.class, *args, &proc)
end