fauna 0.0.0 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+
2
+ module Fauna
3
+ class Follow < Fauna::Model
4
+ def self.find_by_follower_and_resource(follower, resource)
5
+ find(new(:follower => follower, :resource => resource).ref)
6
+ end
7
+
8
+ def initialize(attrs = {})
9
+ super({})
10
+ attrs.stringify_keys!
11
+ follower_ref = attrs['follower_ref']
12
+ follower_ref = attrs['follower'].ref if attrs['follower']
13
+ resource_ref = attrs['resource_ref']
14
+ resource_ref = attrs['resource'].ref if attrs['resource']
15
+ ref = "#{follower_ref}/follows/#{resource_ref}"
16
+
17
+ raise ArgumentError, "Follower ref is nil." if follower_ref.nil?
18
+ raise ArgumentError, "Resource ref is nil." if resource_ref.nil?
19
+
20
+ @struct = { 'ref' => ref, 'follower' => follower_ref, 'resource' => resource_ref }
21
+ end
22
+
23
+ def follower_ref
24
+ struct['follower']
25
+ end
26
+
27
+ def follower
28
+ Fauna::Resource.find(follower_ref)
29
+ end
30
+
31
+ def resource_ref
32
+ struct['resource']
33
+ end
34
+
35
+ def resource
36
+ Fauna::Resource.find(resource_ref)
37
+ end
38
+
39
+ def update(*args)
40
+ raise Fauna::Invalid, "Follows have nothing to update."
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Fauna
3
+ class Publisher < Fauna::Model
4
+ def self.find
5
+ super("publisher")
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,92 @@
1
+
2
+ module Fauna
3
+ class TimelineEvent
4
+
5
+ attr_reader :ts, :timeline_ref, :resource_ref, :action
6
+
7
+ def initialize(attrs)
8
+ # TODO v1
9
+ # @ts = attrs['ts']
10
+ # @timeline_ref = attrs['timeline']
11
+ # @resource_ref = attrs['resource']
12
+ # @action = attrs['action']
13
+ @ts, @action, @resource_ref = *attrs
14
+ end
15
+
16
+ def resource
17
+ Fauna::Resource.find(resource_ref)
18
+ end
19
+
20
+ def timeline
21
+ Timeline.new(timeline_ref)
22
+ end
23
+ end
24
+
25
+ class TimelinePage < Fauna::Resource
26
+ def events
27
+ @events ||= struct['events'].map { |e| TimelineEvent.new(e) }
28
+ end
29
+
30
+ def any?
31
+ struct['events'].any?
32
+ end
33
+
34
+ def resources
35
+ # TODO duplicates can exist in the local timeline. remove w/ v1
36
+ seen = {}
37
+ events.inject([]) do |a, ev|
38
+ if (ev.action == 'create' && !seen[ev.resource_ref])
39
+ seen[ev.resource_ref] = true
40
+ a << ev.resource
41
+ end
42
+
43
+ a
44
+ end
45
+ end
46
+ end
47
+
48
+ class Timeline
49
+ attr_reader :ref
50
+
51
+ def initialize(ref)
52
+ @ref = ref
53
+ end
54
+
55
+ def page(query = nil)
56
+ TimelinePage.find(ref, query)
57
+ end
58
+
59
+ def events(query = nil)
60
+ page(query).events
61
+ end
62
+
63
+ def resources(query = nil)
64
+ page(query).resources
65
+ end
66
+
67
+ def add(resource)
68
+ self.class.add(ref, resource)
69
+ end
70
+
71
+ def remove(resource)
72
+ self.class.remove(ref, resource)
73
+ end
74
+
75
+ def self.add(ref, resource)
76
+ resource = resource.ref if resource.respond_to?(:ref)
77
+ Fauna::Client.post(ref, 'resource' => resource)
78
+ end
79
+
80
+ def self.remove(ref, resource)
81
+ resource = resource.ref if resource.respond_to?(:ref)
82
+ Fauna::Client.delete(ref, 'resource' => resource)
83
+ end
84
+ end
85
+
86
+ class TimelineSettings < Fauna::Resource
87
+ def initialize(name, attrs = {})
88
+ super(attrs)
89
+ struct['ref'] = "timelines/#{name}"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module Fauna
3
+ class User < Fauna::Model
4
+
5
+ validates :name, :presence => true
6
+
7
+ class Settings < Fauna::Model; end
8
+
9
+ def self.self
10
+ find("users/self")
11
+ end
12
+
13
+ def self.find_by_email(email)
14
+ find_by("users", :email => email)
15
+ end
16
+
17
+ def self.find_by_external_id(external_id)
18
+ find_by("users", :external_id => external_id)
19
+ end
20
+
21
+ def self.find_by_name(name)
22
+ find_by("users", :name => name)
23
+ end
24
+
25
+ def email; struct['email']; end
26
+
27
+ def password; struct['password']; end
28
+
29
+ # FIXME https://github.com/fauna/issues/issues/16
30
+ def name
31
+ struct['name']
32
+ end
33
+
34
+ def settings
35
+ Fauna::User::Settings.find("#{ref}/settings")
36
+ end
37
+
38
+ private
39
+
40
+ def post
41
+ Fauna::Client.post("users", struct)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,81 @@
1
+ require 'fauna'
2
+
3
+ # Various and sundry Rails integration points
4
+
5
+ if defined?(Rails)
6
+ module Fauna
7
+ mattr_accessor :root_connection
8
+ mattr_accessor :connection
9
+
10
+ @silent = false
11
+
12
+ CONFIG_FILE = "#{Rails.root}/config/fauna.yml"
13
+ LOCAL_CONFIG_FILE = "#{ENV["HOME"]}/.fauna.yml"
14
+ APP_NAME = Rails.application.class.name.split("::").first.underscore
15
+
16
+ def self.auth!
17
+ if File.exist? CONFIG_FILE
18
+ credentials = YAML.load_file(CONFIG_FILE)[Rails.env] || {}
19
+
20
+ if File.exist? LOCAL_CONFIG_FILE
21
+ credentials.merge!((YAML.load_file(LOCAL_CONFIG_FILE)[APP_NAME] || {})[Rails.env] || {})
22
+ end
23
+
24
+ if !@silent
25
+ STDERR.puts ">> Using Fauna account #{credentials["email"].inspect} for #{APP_NAME.inspect}."
26
+ STDERR.puts ">> You can change this in config/fauna.yml or ~/.fauna.yml."
27
+ end
28
+
29
+ self.root_connection = Connection.new(
30
+ :email => credentials["email"],
31
+ :password => credentials["password"],
32
+ :logger => Rails.logger)
33
+
34
+ publisher_key = root_connection.post("keys/publisher")["resource"]["key"]
35
+ self.connection = Connection.new(publisher_key: publisher_key, logger: Rails.logger)
36
+ else
37
+ if !@silent
38
+ STDERR.puts ">> Fauna account not configured. You can add one in config/fauna.yml."
39
+ end
40
+ end
41
+
42
+ @silent = true
43
+ nil
44
+ end
45
+ end
46
+
47
+ Fauna.auth!
48
+
49
+ # Around filter to set up a default context
50
+
51
+ if Fauna.connection && defined?(ActionController::Base)
52
+ ApplicationController
53
+
54
+ class ApplicationController
55
+ around_filter :default_fauna_context
56
+
57
+ def default_fauna_context
58
+ Fauna::Client.context(Fauna.connection) { yield }
59
+ end
60
+ end
61
+ end
62
+
63
+ # ActionDispatch's Auto reloader blows away some of Fauna's schema
64
+ # configuration that does not live within the Model classes
65
+ # themselves. Add a callback to Reloader to reload the schema config
66
+ # before each request.
67
+
68
+ if defined? ActionDispatch::Reloader
69
+ ActionDispatch::Reloader.to_prepare do
70
+ Fauna.configure_schema!
71
+ end
72
+ end
73
+
74
+ # ActiveSupport::Inflector's 'humanize' method handles the _id
75
+ # suffix for association fields, but not _ref.
76
+ if defined? ActiveSupport::Inflector
77
+ ActiveSupport::Inflector.inflections do |inflect|
78
+ inflect.human /_ref$/i, ''
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,239 @@
1
+ module Fauna
2
+ class Resource
3
+
4
+ def self.fields; @fields ||= [] end
5
+ def self.timelines; @timelines ||= [] end
6
+ def self.references; @references ||= [] end
7
+
8
+ # config DSL
9
+
10
+ class << self
11
+ attr_accessor :fauna_class_name
12
+
13
+ private
14
+
15
+ def field(*names)
16
+ names.each do |name|
17
+ name = name.to_s
18
+ fields << name
19
+ fields.uniq!
20
+
21
+ define_method(name) { data[name] }
22
+ define_method("#{name}=") { |value| data[name] = value }
23
+ end
24
+ end
25
+
26
+ def timeline(*names)
27
+ args = names.last.is_a?(Hash) ? names.pop : {}
28
+
29
+ names.each do |name|
30
+ timeline_name = args[:internal] ? name.to_s : "timelines/#{name}"
31
+ timelines << timeline_name
32
+ timelines.uniq!
33
+
34
+ define_method(name.to_s) { Fauna::Timeline.new("#{ref}/#{timeline_name}") }
35
+ end
36
+ end
37
+
38
+ def reference(*names)
39
+ names.each do |name|
40
+ name = name.to_s
41
+ references << name
42
+ references.uniq!
43
+
44
+ define_method("#{name}_ref") { references[name] }
45
+ define_method("#{name}_ref=") { |ref| (ref.nil? || ref.empty?) ? references.delete(name) : references[name] = ref }
46
+
47
+ define_method(name) { Fauna::Resource.find(references[name]) if references[name] }
48
+ define_method("#{name}=") { |obj| obj.nil? ? references.delete(name) : references[name] = obj.ref }
49
+ end
50
+ end
51
+
52
+ # secondary index helper
53
+
54
+ def find_by(ref, query)
55
+ # TODO elimate direct manipulation of the connection
56
+ response = Fauna::Client.this.connection.get(ref, query)
57
+ response['resources'].map { |attributes| alloc(attributes) }
58
+ rescue Fauna::Connection::NotFound
59
+ []
60
+ end
61
+ end
62
+
63
+ def self.find(ref, query = nil)
64
+ res = Fauna::Client.get(ref, query)
65
+ Fauna.class_for_name(res.fauna_class_name).alloc(res.to_hash)
66
+ end
67
+
68
+ def self.create(*args)
69
+ new(*args).tap { |obj| obj.save }
70
+ end
71
+
72
+ def self.create!(*args)
73
+ new(*args).tap { |obj| obj.save! }
74
+ end
75
+
76
+ def self.alloc(struct)
77
+ obj = allocate
78
+ obj.instance_variable_set('@struct', struct)
79
+ obj
80
+ end
81
+
82
+ attr_reader :struct
83
+
84
+ alias :to_hash :struct
85
+
86
+ def initialize(attrs = {})
87
+ @struct = { 'ref' => nil, 'ts' => nil, 'deleted' => false }
88
+ assign(attrs)
89
+ end
90
+
91
+ def ref; struct['ref'] end
92
+ def ts; struct['ts'] end
93
+ def deleted; struct['deleted'] end
94
+ def external_id; struct['external_id'] end
95
+ def data; struct['data'] ||= {} end
96
+ def references; struct['references'] ||= {} end
97
+ def changes; Timeline.new("#{ref}/changes") end
98
+ def user_follows; Timeline.new("#{ref}/follows/users") end
99
+ def user_followers; Timeline.new("#{ref}/followers/users") end
100
+ def instance_follows; Timeline.new("#{ref}/follows/instances") end
101
+ def instance_followers; Timeline.new("#{ref}/followers/instances") end
102
+ def local; Timeline.new("#{ref}/local") end
103
+
104
+ def eql?(other)
105
+ self.class.equal?(other.class) && self.ref == other.ref && self.ref != nil
106
+ end
107
+ alias :== :eql?
108
+
109
+
110
+ # dynamic field access
111
+
112
+ def respond_to?(method, *args)
113
+ !!getter_method(method) || !!setter_method(method) || super
114
+ end
115
+
116
+ def method_missing(method, *args)
117
+ if field = getter_method(method)
118
+ struct[field]
119
+ elsif field = setter_method(method)
120
+ struct[field] = args.first
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ # object lifecycle
127
+
128
+ def new_record?; ref.nil? end
129
+
130
+ def deleted?; deleted end
131
+
132
+ alias :destroyed? :deleted?
133
+
134
+ def persisted?; !(new_record? || deleted?) end
135
+
136
+ def errors
137
+ @errors ||= ActiveModel::Errors.new(self)
138
+ end
139
+
140
+ def save
141
+ @struct = (new_record? ? post : put).to_hash
142
+ true
143
+ rescue Fauna::Connection::BadRequest => e
144
+ e.param_errors.each { |field, message| errors[field] = message }
145
+ false
146
+ end
147
+
148
+ def save!
149
+ save || (raise Invalid, errors.full_messages)
150
+ end
151
+
152
+ def update(attributes = {})
153
+ assign(attributes)
154
+ save
155
+ end
156
+
157
+ def update!(attributes = {})
158
+ assign(attributes)
159
+ save!
160
+ end
161
+
162
+ def delete
163
+ Fauna::Client.delete(ref) if persisted?
164
+ struct['deleted'] = true
165
+ struct.freeze
166
+ nil
167
+ rescue Fauna::Connection::NotAllowed
168
+ raise Invalid, "This resource can not be destroyed."
169
+ end
170
+
171
+ alias :destroy :delete
172
+
173
+ # TODO eliminate/simplify once v1 drops
174
+
175
+ def fauna_class_name
176
+ @_fauna_class_name ||=
177
+ case ref
178
+ when %r{^users/[^/]+$}
179
+ "users"
180
+ when %r{^instances/[^/]+$}
181
+ "classes/#{struct['class']}"
182
+ when %r{^[^/]+/[^/]+/follows/[^/]+/[^/]+$}
183
+ "follows"
184
+ when %r{^.+/timelines/[^/]+$}
185
+ "timelines"
186
+ when %r{^.+/changes$}
187
+ "timelines"
188
+ when %r{^.+/local$}
189
+ "timelines"
190
+ when %r{^.+/follows/[^/]+$}
191
+ "timelines"
192
+ when %r{^.+/followers/[^/]+$}
193
+ "timelines"
194
+ when %r{^timelines/[^/]+$}
195
+ "timelines/settings"
196
+ when %r{^classes/[^/]+$}
197
+ "classes"
198
+ when %r{^users/[^/]+/settings$}
199
+ "users/settings"
200
+ when "publisher/settings"
201
+ "publisher/settings"
202
+ when "publisher"
203
+ "publisher"
204
+ else
205
+ nil
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ # TODO: make this configurable, and possible to invert to a white list
212
+ UNASSIGNABLE_ATTRIBUTES = %w(ref ts deleted).inject({}) { |h, attr| h.update attr => true }
213
+
214
+ def assign(attributes)
215
+ attributes.each do |name, val|
216
+ send "#{name}=", val unless UNASSIGNABLE_ATTRIBUTES[name.to_s]
217
+ end
218
+ end
219
+
220
+ def put
221
+ Fauna::Client.put(ref, struct)
222
+ rescue Fauna::Connection::NotAllowed
223
+ raise Invalid, "This resource type can not be updated."
224
+ end
225
+
226
+ def post
227
+ raise Invalid, "This resource type can not be created."
228
+ end
229
+
230
+ def getter_method(method)
231
+ field = method.to_s
232
+ struct.include?(field) ? field : nil
233
+ end
234
+
235
+ def setter_method(method)
236
+ (/(.*)=$/ =~ method.to_s) ? $1 : nil
237
+ end
238
+ end
239
+ end