picatrix 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e2e397e99116fa0b237db3f59a35ab0c7521f4e
4
- data.tar.gz: 835f2a685518c5448ad260577949244cf1feca77
3
+ metadata.gz: c1526c5c727bb983c60d9c3240984054c96187de
4
+ data.tar.gz: 6af84973981466c805aa680fad9ca915353418f8
5
5
  SHA512:
6
- metadata.gz: 0cbd222ea637fe500cc864a89dcb244e743405d83b31cadbb1e6b891637c50a8a898a83e9f39b5426d514fb955f3d36be5823d89d9087c5b08150a485bf6c9f8
7
- data.tar.gz: a36dc255cf23bc5305af1edaf7299bd65db0a6a9b4259e138fa1ecbbbf0d322c9d8beff005812a4fdbbcecdf6aca9db81c128d46208961284e9c990396a18e3a
6
+ metadata.gz: b3feed7709d09d6b5bd06d3d88cc09de6a054bee63d24dd9cf62c01f80f356fbb0cdb20f6530c9b950f8c2fc8320273020ebeaae6892fbfc2ad7e51362c021d6
7
+ data.tar.gz: 7f399c285ac1dde603ca580a3fdc2fe2fca415bc17dd24ef3a75f4ea2cfe5cd5b31c56175edc1840817349fc41c51e4ebce3ddbd16559ba09d340a3360ee7b94
data/README.md CHANGED
@@ -10,7 +10,16 @@
10
10
  An opinionated hypermedia API generator.
11
11
 
12
12
  Requires:
13
- - valid `.dot` file (with some specific styles)
13
+ - valid `.dot` file with the following additional style guidelines
14
+ - edge style defaults to `arrowhead="odiamond"` to signify default requests are `GET`
15
+ - the first node listed should be the root, and is signified by `xlabel=` and `shape=point`
16
+ - collection nodes should be given `shape=folder` and pluralized
17
+ - the item node belonging to the collection node should be its singularized version and `shape=rectangle`
18
+ - forms (often called templates in the code, because they are a template for a valid request to the server) are given a `_template` suffix and `shape=note`
19
+ - `style=dotted` means the related object will be represented inline (embedded)
20
+ - `style=bold` and `arrowhead=normal` means `POST`
21
+ - `arrowhead=normal` alone means `PATCH`
22
+ - `color=red fontcolor=red` means `DELETE`
14
23
 
15
24
  Outputs:
16
25
  - Sinatra style routes
@@ -21,7 +30,7 @@ Outputs:
21
30
 
22
31
  ## About
23
32
 
24
- Directed graphs get complicated, but can be divded into simpler subgraphs. Picatrix is currently useful for only a single workflow (and barely that right now, as we're way before a real release). Integrated workflows will involve generating several route sets that will be required into a single Sinatra app. Some workflows may even be representable as DAGs/trees.
33
+ Directed graphs get complicated, but can be divided into simpler subgraphs. Picatrix is currently useful for only a single workflow (and barely that right now, as we're way before a real release). Integrated workflows will involve generating several route sets that will be required into a single Sinatra app. Some workflows may even be representable as DAGs/trees.
25
34
 
26
35
  The ideal way to use Picatrix is to do an exercise in designing your API "outside in" where all inputs/outputs are defined, and a state diagram is drawn. This diagram can be translated to a dot file, and the inputs/outputs can be translated into hypermedia assets.
27
36
 
@@ -57,6 +66,14 @@ $ bin/picapica dg_api.dot app_name
57
66
 
58
67
  ![state diagram image of sample app](spec/support/dg_api.dot.png "simple photo viewing app")
59
68
 
69
+ ### how to interpret the graph
70
+
71
+ Nodes and edges translate roughly into application and/or resource states, and state transitions.
72
+
73
+ Dotted lines represent inlined relations. So, `View submissions` inlines `View submission` by `:item_id` (in this example a number).
74
+
75
+ Labels on edges are link relations. Confusingly the actual _URL_ of the state transition (moving to the next state) is still the target node (essentially, think of the state transition name as the same as the name of the ideal output) _and_ its link relation to its source. So, `A (make_B_do_x)-> B` is `B/make_B_do_x`. This is still (obviously) sketchy.
76
+
60
77
  ## Development
61
78
 
62
79
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -13,11 +13,7 @@ module Picatrix
13
13
  rule(:default_definitions) do
14
14
  indent >>
15
15
  str("edge [") >>
16
- newline >>
17
- indent.repeat >>
18
- str("arrowhead=\"odiamond\"") >>
19
- newline >>
20
- indent >>
16
+ (str("]").absent? >> any).repeat(0) >>
21
17
  str("]") >>
22
18
  newline
23
19
  end
@@ -34,8 +30,8 @@ module Picatrix
34
30
  end
35
31
  rule(:style) do
36
32
  str("style=dotted") |
37
- str("style=bold") |
38
- delete_marker
33
+ str("style=bold") |
34
+ delete_marker
39
35
  end
40
36
  rule(:post_marker) do
41
37
  str("arrowhead=normal")
@@ -45,8 +41,8 @@ module Picatrix
45
41
  end
46
42
  rule(:shape) do
47
43
  str("shape=note") |
48
- str("shape=folder") |
49
- str("shape=rectangle")
44
+ str("shape=folder") |
45
+ str("shape=rectangle")
50
46
  end
51
47
  rule(:label) do
52
48
  str("[") >>
@@ -77,16 +73,12 @@ module Picatrix
77
73
  space.repeat(0) >>
78
74
  str("}")
79
75
  end
80
- rule(:root_label) do
81
- str("[xlabel=") >>
82
- (str("shape=point]").absent? >> any).repeat >>
83
- str("shape=point]")
84
- end
85
76
  rule(:root_line) do
86
77
  indent >>
87
- snake_case_key >>
78
+ str("root") >>
88
79
  space >>
89
- root_label
80
+ (newline.absent? >> any).repeat(0) >>
81
+ newline
90
82
  end
91
83
  rule(:digraph_open_tag) { str("digraph d {") }
92
84
  rule(:digraph_close_tag) { str("}") >> newline.repeat(0) >> eof }
@@ -100,8 +92,7 @@ module Picatrix
100
92
  root_of_api
101
93
  end
102
94
  rule(:root_of_api) do
103
- root_line.as(:initial_inbound_request) >>
104
- newline.repeat(1) >>
95
+ root_line >>
105
96
  (
106
97
  digraph_close_tag.absent? >>
107
98
  (
@@ -7,82 +7,92 @@ require_all("./lib/picatrix/generated/*.rb")
7
7
  class App < Sinatra::Base
8
8
  set :public_folder => "public", :static => true
9
9
 
10
+ get '/rels' do
11
+ json([{"root"=>{:target=>"view_submissions", :link_relation=>"\"newest\"", :inline=>false, :method=>:get}}, {"view_submissions"=>{:target=>"view_submission", :link_relation=>"\":item_id\"", :inline=>true, :method=>:get}}, {"view_submissions"=>{:target=>"submission_filter_template", :link_relation=>"\"filter\"", :inline=>true, :method=>:get}}, {"submission_filter_template"=>{:target=>"view_submissions", :link_relation=>"\"filter\"", :inline=>false, :method=>:get}}, {"view_submissions"=>{:target=>"submission_add_template", :link_relation=>"\"add\"", :inline=>true, :method=>:get}}, {"submission_add_template"=>{:target=>"view_submission", :link_relation=>"\"submit\"", :inline=>false, :method=>:post}}, {"view_submission"=>{:target=>"view_submissions", :link_relation=>"\"newest\"", :inline=>false, :method=>:get}}, {"view_submission"=>{:target=>"submission_delete_template", :link_relation=>"\"remove\"", :inline=>true, :method=>:get}}, {"submission_delete_template"=>{:target=>"view_submissions", :link_relation=>"\"newest\"", :inline=>false, :method=>:delete}}, {"view_submission"=>{:target=>"submission_edit_template", :link_relation=>"\"edit\"", :inline=>true, :method=>:get}}, {"submission_edit_template"=>{:target=>"view_submission", :link_relation=>"\"self\"", :inline=>false, :method=>:patch}}])
12
+ end
13
+
10
14
  get '/' do
11
- status, headers, body = call env.merge("PATH_INFO" => "/welcome/newest")
15
+ status, headers, body = call env.merge("PATH_INFO" => "/view_submissions/newest")
12
16
  [status, headers, body]
13
17
  end
18
+
19
+
20
+
21
+ get "/view_submissions/newest" do
22
+ # get collection
23
+ items = ViewSubmission.all
24
+ # render collection into templates
25
+ hashed_items = items.collect do |item|
26
+ {"@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
27
+ end
28
+ # return that collection
29
+ json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/view_submissions/newest"}, "s:filter"=>{"href"=>"http://localhost:9292/view_submissions/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "s:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}}.merge({"view_submissions" => hashed_items}))
30
+ end
14
31
 
15
- get "/welcome/newest" do
16
- items = ViewSubmission.all
17
- hashed_items = items.collect do |item|
18
- item_id = item.id
19
- category = item.category
20
- photo = item.photo
21
- {"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
22
- end
23
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/welcome/newest"}, "is:filter"=>{"href"=>"http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "is:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}}.merge({"view_submissions" => hashed_items}))
24
- end
25
-
26
- get "/submission_filter_template/filter" do
27
- items = ViewSubmission.all
28
- hashed_items = items.collect do |item|
29
- item_id = item.id
30
- category = item.category
31
- photo = item.photo
32
- {"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
33
- end
34
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/welcome/newest"}, "is:filter"=>{"href"=>"http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "is:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, "category"=>"#{params[:category]}", "has_photo"=>"#{params[:has_photo]}"}.merge({"view_submissions" => hashed_items}))
35
- end
36
-
37
- post "/submission_add_template/submit" do
38
- items = ViewSubmission.all
39
- hashed_items = items.collect do |item|
40
- item_id = item.id
41
- category = item.category
42
- photo = item.photo
43
- {"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
44
- end
45
- json({"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}", :message=>0}.merge({"view_submissions" => hashed_items}))
46
- end
47
-
48
- get "/view_submission/newest" do
49
- items = ViewSubmission.all
50
- hashed_items = items.collect do |item|
51
- item_id = item.id
52
- category = item.category
53
- photo = item.photo
54
- {"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
55
- end
56
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}.merge({"view_submissions" => hashed_items}))
57
- end
32
+
33
+
34
+ get "/view_submissions/filter" do
35
+ # get collection
36
+ items = ViewSubmission.all
37
+ # render collection into templates
38
+ hashed_items = items.collect do |item|
39
+ {"@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
40
+ end
41
+ # return that collection
42
+ json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/view_submissions/newest"}, "s:filter"=>{"href"=>"http://localhost:9292/view_submissions/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "s:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, "category"=>"#{params[:category]}", "has_photo"=>"#{params[:has_photo]}"}.merge({"view_submissions" => hashed_items}))
43
+ end
58
44
 
59
- delete "/submission_delete_template/newest" do
60
- items = ViewSubmission.all
61
- hashed_items = items.collect do |item|
62
- item_id = item.id
63
- category = item.category
64
- photo = item.photo
65
- {"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
66
- end
67
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/welcome/newest"}, "is:filter"=>{"href"=>"http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "is:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, :message=>0}.merge({"view_submissions" => hashed_items}))
68
- end
45
+
46
+
47
+ post "/view_submission/submit" do
48
+ # create item
49
+ item = ViewSubmission.create(params)
50
+ # return new item
51
+ json({"@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/view_submissions/newest"}, "s:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "s:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}", :message=>0})
52
+ end
69
53
 
70
- patch "/submission_edit_template/self" do
71
- items = ViewSubmission.all
72
- hashed_items = items.collect do |item|
73
- item_id = item.id
74
- category = item.category
75
- photo = item.photo
76
- {"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
77
- end
78
- json({"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}", :message=>0}.merge({"view_submissions" => hashed_items}))
79
- end
54
+
55
+
56
+ get "/view_submissions/newest" do
57
+ # get collection
58
+ items = ViewSubmission.all
59
+ # render collection into templates
60
+ hashed_items = items.collect do |item|
61
+ {"@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
62
+ end
63
+ # return that collection
64
+ json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/view_submissions/newest"}, "s:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "s:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}.merge({"view_submissions" => hashed_items}))
65
+ end
80
66
 
67
+
68
+
69
+ delete "/view_submissions/newest" do
70
+ # get collection
71
+ items = ViewSubmission.all
72
+ # render collection into templates
73
+ hashed_items = items.collect do |item|
74
+ {"@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"}
75
+ end
76
+ # return that collection
77
+ json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/view_submissions/newest"}, "s:filter"=>{"href"=>"http://localhost:9292/view_submissions/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "s:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, :message=>0}.merge({"view_submissions" => hashed_items}))
78
+ end
81
79
 
82
- get "/view_submission/:item_id" do
83
- item_id = params[:item_id]
84
- item = ViewSubmission.find(item_id)
85
- json({"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"})
86
- end
80
+
81
+
82
+ patch "/view_submission/self" do
83
+ # create item
84
+ item = ViewSubmission.find(params)
85
+ # return new item
86
+ json({"@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/view_submissions/newest"}, "s:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "s:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}", :message=>0})
87
+ end
87
88
 
89
+
90
+
91
+
92
+ get "/view_submission/:item_id" do
93
+ item_id = params[:item_id]
94
+ item = ViewSubmission.find(item_id)
95
+ json({"@namespaces"=>{"s"=>{"name"=>"http://localhost:9292/rels#"}}, "@controls"=>{"self"=>{"title"=>"submission #{item.id}", "href"=>"http://localhost:9292/view_submission/#{item.id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/view_submissions/newest"}, "s:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "s:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item.id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"})
96
+ end
97
+
88
98
  end
@@ -7,6 +7,9 @@ class ViewSubmission
7
7
  id: item_id
8
8
  )
9
9
  end
10
+ def self.create(params)
11
+ self.find(rand(10))
12
+ end
10
13
  def self.all
11
14
  [
12
15
  OpenStruct.new(
@@ -5,35 +5,39 @@ module Picatrix
5
5
  class Jenny
6
6
  include Thor::Base
7
7
  include Thor::Actions
8
- attr_accessor :edges, :nodes, :app_name, :routes, :root,
9
- :destination_stack, :options, :root_node_name,
10
- :default_response, :assets, :addressable_items,
11
- :generated
8
+
9
+ attr_accessor :app_name, :routes, :root_path, :rels,
10
+ :destination_stack, :options,
11
+ :default_response, :assets, :generated,
12
+ :addressable_items, :collectable_items
12
13
 
13
14
  argument :name
14
15
  source_root File.dirname(__FILE__)
15
16
 
16
17
  def initialize(array = [], options = {}, app_name = "app")
17
- @app_name = app_name
18
+ # thor related
18
19
  @options = options
19
20
  @destination_stack = [self.class.source_root]
20
- @default_response = { message: 0 }
21
21
 
22
22
  hash = array.group_by {|e| e.keys.first}
23
23
  @nodes = hash[:node].flat_map {|e| e[:node]}.reduce({}, :merge)
24
24
  @edges = hash[:edge].flat_map {|e| e[:edge]}
25
- # TODO need to adjust parser down below so i dont need to traverse this nonsense
26
- @root_node_name = hash[:root].first.values.flatten.first.split("[").first.delete(" ")
25
+
26
+ @app_name = app_name
27
+ @rels = @edges
28
+ @default_response = { message: 0 }
27
29
 
28
30
  @generated = []
29
- # TODO for performance combine loops
30
31
  @assets = assets
32
+ # items must come before collectable_items
31
33
  @addressable_items = addressable_items
34
+ @collectable_items = collectable_items
32
35
  @routes = routes
33
36
  end
34
37
 
35
38
  def just_do_it
36
39
  @generated.each do |node_name|
40
+ # bring in defined objects
37
41
  require "./lib/picatrix/generated/#{node_name}.rb"
38
42
  end
39
43
  generate_app_file
@@ -47,16 +51,12 @@ module Picatrix
47
51
  )
48
52
  end
49
53
  def assets
50
- @edges.collect do | edge|
54
+ @assets ||= @edges.collect do | edge|
51
55
  source = edge.keys.first
52
56
  properties = edge[source]
53
57
  # if you dont have an inline asset, ignore
54
58
  next unless properties[:inline]
55
59
 
56
- # TODO ?
57
- #link_relation = properties[:link_relation]
58
- #path = "#{source}/#{link_relation.delete('"')}"
59
-
60
60
  mason_path = "lib/picatrix/templates/mason/#{properties[:target]}.json"
61
61
 
62
62
  if File.exist?(mason_path)
@@ -74,14 +74,20 @@ module Picatrix
74
74
  end.compact.reduce({}, :merge)
75
75
  end
76
76
  def routes
77
- @edges.collect do |edge|
77
+ @routes ||= @edges.collect do |edge|
78
78
  source = edge.keys.first
79
+ target = edge.values.first[:target]
79
80
  properties = edge[source]
81
+
80
82
  # we dont need a route if it is inline represented
81
83
  next if properties[:inline]
82
84
 
83
85
  link_relation = properties[:link_relation]
84
- path = "#{source}/#{link_relation.delete('"')}"
86
+ path = "#{target}/#{link_relation.delete('"')}"
87
+
88
+ if source == "root"
89
+ @root_path = path
90
+ end
85
91
 
86
92
  mason_path = "lib/picatrix/templates/mason/#{edge[source][:target]}.json"
87
93
 
@@ -95,36 +101,21 @@ module Picatrix
95
101
  body = default_response
96
102
  end
97
103
 
98
- if source == root_node_name
99
- @root = path
100
- end
101
-
102
104
  if (asset = assets[source]) && body.is_a?(Hash)
103
105
  body.merge!(asset)
104
106
  end
105
107
 
106
- collections = @addressable_items.inject({}) do |memo, item|
107
- memo["#{path}"] = {
108
- name: "#{item[:type].pluralize}",
109
- resource_name: "#{item[:type].camelcase}",
110
- template: JSON.parse(
111
- File.read("lib/picatrix/templates/mason/#{item[:type]}_min.json")
112
- )
113
- }
114
- memo
115
- end
116
-
117
108
  {
118
109
  path: path,
119
110
  method: properties[:method],
120
111
  # danger danger danger will robinson we are unescaping here!
121
112
  body: body.to_s.delete("\\"),
122
- collection: collections["#{path}"]
113
+ collection: (collectable_items[target.singularize] || [])
123
114
  }
124
115
  end.compact!
125
116
  end
126
117
  def addressable_items
127
- @nodes.collect do |node|
118
+ @addressable_items ||= @nodes.collect do |node|
128
119
  node_name = node.first
129
120
  node_properties = node.last
130
121
 
@@ -160,7 +151,6 @@ module Picatrix
160
151
  else
161
152
  schema = {}
162
153
  end
163
-
164
154
  {
165
155
  path: item_path,
166
156
  type: node_name,
@@ -171,5 +161,31 @@ module Picatrix
171
161
  end
172
162
  end.compact!
173
163
  end
164
+ def collectable_items
165
+ @collectable_items ||= @addressable_items.inject({}) do |memo, item|
166
+ memo[item[:type]] = {
167
+ name: "#{item[:type].pluralize}",
168
+ resource_name: "#{item[:type].camelcase}",
169
+ template: JSON.parse(
170
+ File.read("lib/picatrix/templates/mason/#{item[:type]}_min.json")
171
+ )
172
+ }
173
+ memo
174
+ end
175
+ end
176
+ def return_mapping_of(method)
177
+ {
178
+ post: :single_return,
179
+ get: :collection_return,
180
+ patch: :single_return,
181
+ delete: :collection_return
182
+ }[method]
183
+ end
184
+ def send_method_for(method)
185
+ {
186
+ post: :create,
187
+ patch: :find
188
+ }[method]
189
+ end
174
190
  end
175
191
  end
@@ -4,9 +4,11 @@ module Picatrix
4
4
  attr_accessor :graph, :hash
5
5
 
6
6
  def initialize(file_name)
7
- @graph = GraphViz.parse(file_name, :path => "/usr/local/bin")
7
+ @graph = GraphViz.parse(
8
+ file_name, :path => "/usr/local/bin"
9
+ )
8
10
  @hash = TransformToHash.new.apply(
9
- DigraphParser.new.parse(
11
+ DigraphParser.new.parse_with_debug(
10
12
  File.read(file_name)
11
13
  )
12
14
  ).flat_map(&:entries).
@@ -7,27 +7,42 @@ require_all("./lib/picatrix/generated/*.rb")
7
7
  class <%= @app_name.camelize %> < Sinatra::Base
8
8
  set :public_folder => "public", :static => true
9
9
 
10
+ get '/rels' do
11
+ json(<%= @rels %>)
12
+ end
13
+
10
14
  get '/' do
11
- status, headers, body = call env.merge("PATH_INFO" => "/<%= @root %>")
15
+ status, headers, body = call env.merge("PATH_INFO" => "/<%= @root_path %>")
12
16
  [status, headers, body]
13
17
  end
14
- <% @routes.each do |route| %>
15
- <%= route[:method] %> "/<%= route[:path] %>" do
16
- items = <%= route[:collection][:resource_name].constantize %>.all
17
- hashed_items = items.collect do |item|
18
- item_id = item.id
19
- category = item.category
20
- photo = item.photo
21
- <%= route[:collection][:template].to_s.delete("\\") %>
22
- end
23
- json(<%= route[:body] %>.merge({"<%= route[:collection][:name] %>" => hashed_items}))
24
- end
25
- <% end %>
26
- <% @addressable_items.each do |item| %>
27
- get "/<%= item[:path] %>" do
28
- item_id = params[:item_id]
29
- item = <%= item[:type].camelcase.constantize %>.find(item_id)
30
- json(<%= item[:body] %>)
31
- end
18
+
19
+ <% @routes.uniq.each do |route| %>
20
+ <% if return_mapping_of(route[:method]) == :collection_return %>
21
+ <%= route[:method] %> "/<%= route[:path] %>" do
22
+ # get collection
23
+ items = <%= route[:collection][:resource_name].constantize %>.all
24
+ # render collection into templates
25
+ hashed_items = items.collect do |item|
26
+ <%= route[:collection][:template].to_s.delete("\\") %>
27
+ end
28
+ # return that collection
29
+ json(<%= route[:body] %>.merge({"<%= route[:collection][:name] %>" => hashed_items}))
30
+ end
31
+ <% else %>
32
+ <%= route[:method] %> "/<%= route[:path] %>" do
33
+ # create item
34
+ item = <%= route[:collection][:resource_name].constantize %>.<%= send_method_for(route[:method]).to_s %>(params)
35
+ # return new item
36
+ json(<%= route[:body] %>)
37
+ end
32
38
  <% end %>
39
+ <% end %>
40
+
41
+ <% @addressable_items.each do |item| %>
42
+ get "/<%= item[:path] %>" do
43
+ item_id = params[:item_id]
44
+ item = <%= item[:type].camelcase.constantize %>.find(item_id)
45
+ json(<%= item[:body] %>)
46
+ end
47
+ <% end %>
33
48
  end
@@ -1,26 +1,31 @@
1
1
  {
2
+ "@namespaces": {
3
+ "s": {
4
+ "name": "http://localhost:9292/rels#"
5
+ }
6
+ },
2
7
  "@controls": {
3
8
  "self": {
4
- "title": "submission #{item_id}",
5
- "href": "http://localhost:9292/view_submission/#{item_id}"
9
+ "title": "submission #{item.id}",
10
+ "href": "http://localhost:9292/view_submission/#{item.id}"
6
11
  },
7
12
  "up": {
8
13
  "title": "View submissions",
9
- "href": "http://localhost:9292/welcome/newest"
14
+ "href": "http://localhost:9292/view_submissions/newest"
10
15
  },
11
- "is:edit": {
16
+ "s:edit": {
12
17
  "title": "update category or photo url",
13
18
  "encoding": "json",
14
- "href": "http://localhost:9292/view_submission/#{item_id}",
19
+ "href": "http://localhost:9292/view_submission/#{item.id}",
15
20
  "method": "PATCH",
16
21
  "template": {
17
22
  "photo": "{new_url}",
18
23
  "category": "{new_category}"
19
24
  }
20
25
  },
21
- "is:remove": {
26
+ "s:remove": {
22
27
  "title": "remove submission",
23
- "href": "http://localhost:9292/view_submission/#{item_id}",
28
+ "href": "http://localhost:9292/view_submission/#{item.id}",
24
29
  "method": "DELETE"
25
30
  }
26
31
  },
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "@controls": {
3
3
  "self": {
4
- "title": "submission #{item_id}",
5
- "href": "http://localhost:9292/view_submission/#{item_id}"
4
+ "title": "submission #{item.id}",
5
+ "href": "http://localhost:9292/view_submission/#{item.id}"
6
6
  }
7
7
  },
8
8
  "id": "#{item.id}",
@@ -5,18 +5,22 @@
5
5
  "@title": "Submissions",
6
6
  "@description": "This resource represents submissions."
7
7
  },
8
-
8
+ "@namespaces": {
9
+ "s": {
10
+ "name": "http://localhost:9292/rels#"
11
+ }
12
+ },
9
13
  "@controls": {
10
14
  "self": {
11
- "href": "http://localhost:9292/welcome/newest"
15
+ "href": "http://localhost:9292/view_submissions/newest"
12
16
  },
13
- "is:filter": {
14
- "href": "http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}",
17
+ "s:filter": {
18
+ "href": "http://localhost:9292/view_submissions/filter?category={category}&has_photo={has_photo}",
15
19
  "isHrefTemplate": true,
16
20
  "title": "filter submissions by category",
17
21
  "description": "in addition to category, you can ensure the submission has a photo"
18
22
  },
19
- "is:add": {
23
+ "s:add": {
20
24
  "title": "add new submission",
21
25
  "encoding": "json",
22
26
  "href": "http://localhost:9292/submission_add_template/submit",
@@ -7,6 +7,9 @@ class <%= @node_name.camelize %>
7
7
  id: item_id
8
8
  )
9
9
  end
10
+ def self.create(params)
11
+ self.find(rand(10))
12
+ end
10
13
  def self.all
11
14
  [
12
15
  OpenStruct.new(
@@ -1,10 +1,5 @@
1
1
  module Picatrix
2
2
  class TransformToHash < Parslet::Transform
3
- rule(:initial_inbound_request => simple(:x)) do
4
- {
5
- root: String(x)
6
- }
7
- end
8
3
  rule(:node => subtree(:node)) do
9
4
  types = {
10
5
  note: "template",
@@ -1,3 +1,3 @@
1
1
  module Picatrix
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: picatrix
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Moore-Niemi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-11-09 00:00:00.000000000 Z
11
+ date: 2015-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parslet
@@ -226,7 +226,6 @@ files:
226
226
  - bin/setup
227
227
  - lib/picatrix.rb
228
228
  - lib/picatrix/digraph_parser.rb
229
- - lib/picatrix/generated/.rb
230
229
  - lib/picatrix/generated/app.rb
231
230
  - lib/picatrix/generated/view_submission.rb
232
231
  - lib/picatrix/jenny.rb
@@ -1,46 +0,0 @@
1
- require 'sinatra'
2
- require 'sinatra/json'
3
- require 'require_all'
4
-
5
- require_all("./lib/picatrix/generated/*.rb")
6
-
7
- class App < Sinatra::Base
8
- set :public_folder => "public", :static => true
9
-
10
- get '/' do
11
- status, headers, body = call env.merge("PATH_INFO" => "/welcome/newest")
12
- [status, headers, body]
13
- end
14
-
15
- get "/welcome/newest" do
16
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/welcome/newest"}, "is:filter"=>{"href"=>"http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "is:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, "submissions"=>[{"@controls"=>{"self"=>{"title"=>"submission 1", "href"=>"http://localhost:9292/view_submission/1"}}, "id"=>1, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg"}, {"@controls"=>{"self"=>{"title"=>"submission 2", "href"=>"http://localhost:9292/view_submission/2"}}, "id"=>2, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/icqDxNab3Do/maxresdefault.jpg"}]})
17
- end
18
-
19
- get "/submission_filter_template/filter" do
20
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/welcome/newest"}, "is:filter"=>{"href"=>"http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "is:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, "submissions"=>[{"@controls"=>{"self"=>{"title"=>"submission 1", "href"=>"http://localhost:9292/view_submission/1"}}, "id"=>1, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg"}, {"@controls"=>{"self"=>{"title"=>"submission 2", "href"=>"http://localhost:9292/view_submission/2"}}, "id"=>2, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/icqDxNab3Do/maxresdefault.jpg"}], "category"=>"#{params[:category]}", "has_photo"=>"#{params[:has_photo]}"})
21
- end
22
-
23
- post "/submission_add_template/submit" do
24
- json({"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}", :message=>0})
25
- end
26
-
27
- get "/view_submission/newest" do
28
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "submissions"=>[{"@controls"=>{"self"=>{"title"=>"submission 1", "href"=>"http://localhost:9292/view_submission/1"}}, "id"=>1, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg"}, {"@controls"=>{"self"=>{"title"=>"submission 2", "href"=>"http://localhost:9292/view_submission/2"}}, "id"=>2, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/icqDxNab3Do/maxresdefault.jpg"}], "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"})
29
- end
30
-
31
- delete "/submission_delete_template/newest" do
32
- json({"title"=>"View submissions", "description"=>"idk yet", "@meta"=>{"@title"=>"Submissions", "@description"=>"This resource represents submissions."}, "@controls"=>{"self"=>{"href"=>"http://localhost:9292/welcome/newest"}, "is:filter"=>{"href"=>"http://localhost:9292/submission_filter_template/filter?category={category}&has_photo={has_photo}", "isHrefTemplate"=>true, "title"=>"filter submissions by category", "description"=>"in addition to category, you can ensure the submission has a photo"}, "is:add"=>{"title"=>"add new submission", "encoding"=>"json", "href"=>"http://localhost:9292/submission_add_template/submit", "method"=>"POST", "template"=>{"title"=>"default title", "category"=>"cats", "photo_url"=>"http://cats.com"}}}, "submissions"=>[{"@controls"=>{"self"=>{"title"=>"submission 1", "href"=>"http://localhost:9292/view_submission/1"}}, "id"=>1, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg"}, {"@controls"=>{"self"=>{"title"=>"submission 2", "href"=>"http://localhost:9292/view_submission/2"}}, "id"=>2, "category"=>"cats", "photo"=>"https://i.ytimg.com/vi/icqDxNab3Do/maxresdefault.jpg"}], :message=>0})
33
- end
34
-
35
- patch "/submission_edit_template/self" do
36
- json({"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}", :message=>0})
37
- end
38
-
39
-
40
- get "/view_submission/:item_id" do
41
- item_id = params[:item_id]
42
- item = ViewSubmission.find(item_id)
43
- json({"@controls"=>{"self"=>{"title"=>"submission #{item_id}", "href"=>"http://localhost:9292/view_submission/#{item_id}"}, "up"=>{"title"=>"View submissions", "href"=>"http://localhost:9292/welcome/newest"}, "is:edit"=>{"title"=>"update category or photo url", "encoding"=>"json", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"PATCH", "template"=>{"photo"=>"{new_url}", "category"=>"{new_category}"}}, "is:remove"=>{"title"=>"remove submission", "href"=>"http://localhost:9292/view_submission/#{item_id}", "method"=>"DELETE"}}, "id"=>"#{item.id}", "category"=>"#{item.category}", "photo"=>"#{item.photo}"})
44
- end
45
-
46
- end