faceted 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Corey Ehmke
1
+ Copyright (c) 2012 Trunk Club
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,6 +1,105 @@
1
1
  faceted
2
2
  =======
3
3
 
4
- A set of tools, patterns, and modules for use in API implementations.
4
+ Faceted provides set of tools, patterns, and modules for use in API implementations.
5
+
6
+ It was written and is maintained by Corey Ehmke (@bantik) and Max Thom Stahl (@villainous) at Trunk Club.
7
+
8
+ Presenters
9
+ ----------
10
+
11
+ Let's say that you have an ActiveRecord model called Musician, and you want to expose it through your API using a *Presenter* pattern. Faceted makes it easy. Create a new class namespaced inside of your API like so:
12
+
13
+
14
+
15
+ module MyApi
16
+ class Musician
17
+ include Faceted::Presenter
18
+ presents :musician
19
+ field :name
20
+ field :genre
21
+ end
22
+ end
23
+
24
+ That's actually all you have to do. The `presents` method maps your Musician presenter to a root-level class called `Musician`, and the `field` methods map to attributes *or* methods on the associated AR Musician instance.
25
+
26
+ What's that, you say? How is the appropriate AR Musican record associated? Simple. Invoke an instance of the `MyApi::Musician` passing in an `:id` parameter, and it just works:
27
+
28
+ m = Musician.create(:name => 'Johnny Cash', :genre => 'Western')
29
+ m.id
30
+ => 13
31
+
32
+ presenter = MyApi::Musician.new(:id => 13)
33
+ presenter.name
34
+ => "Johnny Cash"
35
+
36
+ You can also invoke methods on AR instances using the same syntax. Let's say that your base `Musician` class has a `random_song_title` method that returns one of the musician's popular songs. Simply wire up the method in your presenter:
37
+
38
+ field :random_song_title
39
+
40
+ That's it.
41
+
42
+ presenter.random_song_title
43
+ => "Ring of Fire"
44
+
45
+ Relationships work almost the same way. If `Musician` actually `has_one` birthplace, and includes a `birthplace_id` attribute, wire it up like this:
46
+
47
+ field :birthplace_id
48
+
49
+ Create a presenter for the associated Birthplace model:
50
+
51
+ module MyApi
52
+ class Birthplace
53
+ include Faceted::Presenter
54
+ presents :birthplace
55
+ field :city
56
+ field :state
57
+ end
58
+ end
59
+
60
+ Now your `Musician` presenter responds the way it should:
61
+
62
+ presenter.birthplace.city
63
+ => "Kingsland"
64
+
65
+ It's smart enough to identify that `birthplace_id` indicates a relationship and builds the association for you. If you don't want it to do this, simply pass the `skip_association` flag:
66
+
67
+ field :record_label_id, :skip_association => true
68
+
69
+ You can also explicitly declare the class of the association:
70
+
71
+ field :genre_id, :class_name => 'MusicalGenre'
72
+
73
+ Collectors
74
+ ----------
75
+ Collectors are simply models that collect multiple instances of another model. An example:
76
+
77
+ module MyApi
78
+ class Playlist
79
+ include Faceted::Collector
80
+ collects :musicians, :find_by => :genre_id
81
+ end
82
+ end
83
+
84
+ l = MyApi::Playlist.new(:genre_id => 3)
85
+ l.musicians.count
86
+ => 14
87
+
88
+ l.musicians.first.name
89
+ => "American Music Club"
90
+
91
+ Contributing to faceted
92
+ =======================
93
+
94
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
95
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
96
+ * Fork the project.
97
+ * Start a feature/bugfix branch.
98
+ * Commit and push until you are happy with your contribution.
99
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
100
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
101
+
102
+ Copyright
103
+ =========
104
+ Copyright (c) 2012 Trunk Club. See LICENSE.txt for further details.
5
105
 
6
- This software is alpha and not production-ready.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
data/faceted.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "faceted"
8
- s.version = "0.5.0"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Corey Ehmke", "Max Thom Stahl"]
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
29
29
  "faceted.gemspec",
30
30
  "lib/faceted.rb",
31
31
  "lib/faceted/collector.rb",
32
+ "lib/faceted/controller.rb",
32
33
  "lib/faceted/presenter.rb",
33
34
  "spec/collector_spec.rb",
34
35
  "spec/presenter_spec.rb",
@@ -19,7 +19,7 @@ module Faceted
19
19
  def collects(name, args={})
20
20
  @collects = eval "#{scope}#{args[:class_name] || name.to_s.classify}"
21
21
  define_method :"#{name.downcase}" do
22
- self.objects
22
+ objects
23
23
  end
24
24
  define_method :finder do
25
25
  {"#{args[:find_by]}" => self.send(args[:find_by])}
@@ -45,15 +45,17 @@ module Faceted
45
45
  self.success = true
46
46
  end
47
47
 
48
+ def to_hash
49
+ objects.map{|o| o.to_hash}
50
+ end
51
+
52
+ private
53
+
48
54
  def objects
49
55
  return unless self.class.collected_class
50
56
  @objects ||= self.class.collected_class.where(self.finder)
51
57
  end
52
58
 
53
- def to_hash
54
- self.objects.map{|o| o.to_hash}
55
- end
56
-
57
59
  end
58
60
 
59
61
  end
@@ -0,0 +1,48 @@
1
+ module Faceted
2
+
3
+ module Controller
4
+
5
+ # For rendering a response with a single object, e.g.
6
+ # render_response(@addresses)
7
+ def render_response(obj)
8
+ render :json => {
9
+ success: obj.success,
10
+ response: obj.to_hash,
11
+ errors: obj.errors
12
+ }.to_json
13
+ end
14
+
15
+ # For rendering a response with a multiple objects, e.g.
16
+ # render_response_with_collection(:addresses, @addresses)
17
+ def render_response_with_collection(key, array)
18
+ render :json => {
19
+ success: true,
20
+ response: {"#{key}".to_sym => array},
21
+ errors: nil
22
+ }.to_json
23
+ end
24
+
25
+ # In your base API controller:
26
+ # rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
27
+ def render_400(exception)
28
+ render :json => {
29
+ success: false,
30
+ response: nil,
31
+ errors: "Record not found: #{exception.message}"
32
+ }, :status => 404
33
+ end
34
+
35
+ # In your base API controller:
36
+ # rescue_from Exception, :with => :render_500
37
+ def render_500(exception)
38
+ Rails.logger.info("!!! #{self.class.name} exception caught: #{exception} #{exception.backtrace.join("\n")}")
39
+ render :json => {
40
+ success: false,
41
+ response: nil,
42
+ errors: "#{exception.message}"
43
+ }, :status => 500
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -5,7 +5,7 @@ module Faceted
5
5
  require 'json'
6
6
  require 'active_support/core_ext/hash'
7
7
 
8
- # Class methods ==========================================================
8
+ # Class methods ===========================================================
9
9
 
10
10
  def self.included(base)
11
11
  base.extend ActiveModel::Naming
@@ -27,6 +27,12 @@ module Faceted
27
27
  end
28
28
  end
29
29
 
30
+ def create(params={})
31
+ obj = self.new(params)
32
+ obj.save
33
+ obj
34
+ end
35
+
30
36
  def field(name, args={})
31
37
 
32
38
  fields << name
@@ -43,12 +49,6 @@ module Faceted
43
49
 
44
50
  end
45
51
 
46
- def create(params={})
47
- object = self.new(params)
48
- object.save
49
- object
50
- end
51
-
52
52
  def fields
53
53
  @fields ||= [:id]
54
54
  end
@@ -67,10 +67,10 @@ module Faceted
67
67
  end
68
68
 
69
69
  def presents(name, args={})
70
- @presents = args[:class_name] || name.to_s.classify
71
- klass = eval "::#{@presents}"
72
- define_method :"#{@presents.downcase}" do
73
- self.object
70
+ class_name = args[:class_name] || name.to_s.classify
71
+ @presents = eval(class_name)
72
+ define_method :"#{class_name.downcase}" do
73
+ object
74
74
  end
75
75
  end
76
76
 
@@ -79,7 +79,7 @@ module Faceted
79
79
  end
80
80
 
81
81
  def where(args)
82
- materialize(eval(presented_class).where(args))
82
+ materialize(presented_class.where(args))
83
83
  end
84
84
 
85
85
  end
@@ -88,20 +88,31 @@ module Faceted
88
88
 
89
89
  def initialize(args={})
90
90
  self.id = args[:id]
91
- self.initialize_with_object
91
+ initialize_with_object
92
92
  ! args.empty? && args.symbolize_keys.delete_if{|k,v| v.nil?}.each{|k,v| self.send("#{k}=", v) if self.respond_to?("#{k}=") && ! v.blank? }
93
93
  self.errors = []
94
94
  self.success = true
95
95
  end
96
96
 
97
+ def save
98
+ schema_fields.each{ |k| object.send("#{k}=", self.send(k)) if object.respond_to?("#{k}=") }
99
+ object.save!
100
+ end
101
+
102
+ def to_hash
103
+ schema_fields.inject({}) {|h,k| h[k] = self.send(k); h}
104
+ end
105
+
106
+ private
107
+
97
108
  def initialize_with_object
98
109
  return unless object
99
- self.class.fields.each{ |k| self.send("#{k}=", self.object.send(k)) if self.respond_to?("#{k}=") }
110
+ schema_fields.each{ |k| self.send("#{k}=", object.send(k)) if self.respond_to?("#{k}=") }
100
111
  end
101
112
 
102
113
  def object
103
114
  return unless self.class.presented_class
104
- @object ||= self.id ? eval(self.class.presented_class).find(self.id) : eval(self.class.presented_class).new
115
+ @object ||= self.id ? self.class.presented_class.find(self.id) : self.class.presented_class.new
105
116
  end
106
117
 
107
118
  def object=(obj)
@@ -109,13 +120,8 @@ module Faceted
109
120
  self.id = obj.id
110
121
  end
111
122
 
112
- def save
113
- self.class.fields.each{ |k| self.object.send("#{k}=", self.send(k)) if self.object.respond_to?("#{k}=") }
114
- self.object.save!
115
- end
116
-
117
- def to_hash
118
- self.class.fields.inject({}) {|h,k| h[k] = self.send(k); h}
123
+ def schema_fields
124
+ self.class.fields
119
125
  end
120
126
 
121
127
  end
@@ -31,7 +31,7 @@ module MyApi
31
31
 
32
32
  class Band
33
33
  include Faceted::Collector
34
- collects :musicians, :class_name => 'Musician', :find_by => :birthplace_id
34
+ collects :musicians, :find_by => :birthplace_id
35
35
  end
36
36
 
37
37
  describe Band do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faceted
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -160,6 +160,7 @@ files:
160
160
  - faceted.gemspec
161
161
  - lib/faceted.rb
162
162
  - lib/faceted/collector.rb
163
+ - lib/faceted/controller.rb
163
164
  - lib/faceted/presenter.rb
164
165
  - spec/collector_spec.rb
165
166
  - spec/presenter_spec.rb
@@ -179,7 +180,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
180
  version: '0'
180
181
  segments:
181
182
  - 0
182
- hash: 3092283735648102372
183
+ hash: 4456343410133406999
183
184
  required_rubygems_version: !ruby/object:Gem::Requirement
184
185
  none: false
185
186
  requirements: