fauna 0.0.0 → 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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