eventbright 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +92 -0
  5. data/Rakefile +52 -0
  6. data/VERSION +1 -0
  7. data/lib/eventbright.rb +5 -0
  8. data/lib/eventbright/api_object.rb +180 -0
  9. data/lib/eventbright/api_object_class_methods.rb +117 -0
  10. data/lib/eventbright/api_object_collection.rb +60 -0
  11. data/lib/eventbright/api_object_relationships.rb +72 -0
  12. data/lib/eventbright/api_objects/attendee.rb +51 -0
  13. data/lib/eventbright/api_objects/discount.rb +62 -0
  14. data/lib/eventbright/api_objects/event.rb +79 -0
  15. data/lib/eventbright/api_objects/organizer.rb +26 -0
  16. data/lib/eventbright/api_objects/ticket.rb +63 -0
  17. data/lib/eventbright/api_objects/user.rb +45 -0
  18. data/lib/eventbright/api_objects/venue.rb +18 -0
  19. data/lib/eventbright/error.rb +14 -0
  20. data/lib/eventbright/main.rb +57 -0
  21. data/spec/eventbright/api_object_class_spec.rb +113 -0
  22. data/spec/eventbright/api_object_collection_spec.rb +67 -0
  23. data/spec/eventbright/api_object_relationship_spec.rb +23 -0
  24. data/spec/eventbright/api_object_spec.rb +15 -0
  25. data/spec/eventbright/user_spec.rb +141 -0
  26. data/spec/eventbright_api_faker.rb +18 -0
  27. data/spec/eventbright_spec.rb +36 -0
  28. data/spec/faked_responses/auth_required.json +1 -0
  29. data/spec/faked_responses/event_get.json +1 -0
  30. data/spec/faked_responses/user_get.json +1 -0
  31. data/spec/faked_responses/user_list_events.json +1 -0
  32. data/spec/faked_responses/user_list_organizers.json +1 -0
  33. data/spec/faked_responses/user_list_venues.json +1 -0
  34. data/spec/faked_responses/user_update.json +1 -0
  35. data/spec/spec.opts +1 -0
  36. data/spec/spec_helper.rb +35 -0
  37. metadata +132 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 David Haslem
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,92 @@
1
+ eventbright
2
+ ================
3
+
4
+ A simple library for integrating with EventBrite's API. Requires the "httparty" gem for connecting and doing XML parsing, and "tzinfo" gem for getting back and forth between timezone names and GMT offsets.
5
+
6
+ Usage
7
+ -----
8
+
9
+ require 'eventbright'
10
+ EventBright.setup("APP_KEY")
11
+ user = EventBright::User.new("USER_KEY") #=> <EventBright::User >
12
+ user.venues #=> [<EventBright::Venue>,...] # Venues the user has defined
13
+
14
+ Authentication
15
+ --------------
16
+ Many methods require user authentication. For these methods, you can pass a user object as an authentication token, and the user's api_key will automatically be used for the request.
17
+
18
+ Example:
19
+
20
+ EventBright::Event.new({"x" => "y"... , "user" => user})
21
+
22
+ Known Bugs
23
+ ----------
24
+
25
+ 1. This library's testing coverage is almost zero. I'm working on it.
26
+ 2. There is no subuser support.
27
+
28
+ A Note About App Keys
29
+ ---------------------
30
+
31
+ This gem has an application key for accessing EventBrite, but each app key is limited to 30,000 requests a day. To make sure your limits aren't affected by others, you should register for your own app key specific to the application you're adding the gem to.
32
+
33
+ If you just want to give the gem a whirl (you have to wait for approval to get your own) just don't call setup. The app key for the gem will be used.
34
+
35
+ Learn more about EventBrite's App Key policy here: [Terms of Service](http://www.eventbrite.com/api/terms)
36
+
37
+ Register for your own app key here: [Request a Key](http://www.eventbrite.com/api/key/)
38
+
39
+
40
+ API Gotchas:
41
+ --------------------
42
+
43
+ A list of sticking points for anyone attempting their own integration with the EventBrite API:
44
+
45
+ __/get => /update variable inconsistencies__
46
+
47
+ * event.id => event.event_id
48
+ * event.timezone (Olson format, ex: "US/Central") => event.timezone (GMT offset hours, ex: "GMT-05")
49
+ * event.privacy (String representing privacy "Private"|"Public") => event.privacy (Boolean 0 = public)
50
+ * event.url => event.personalized_url
51
+ * venue.address => venue.adress
52
+ * venue.address_2 => venue.adress_2
53
+ * venue.name => venue.venue
54
+ * event.tickets.ticket.start_date => ticket.start_sales
55
+ * event.tickets.ticket.end_date => ticket.end_sales
56
+ * event.tickets.ticket.visible (1 is visible) => ticket.hide (y is hidden, n is visible)
57
+ * event.tickets.ticket.quantity_available => ticket.quantity
58
+
59
+ __Fields you can't edit__
60
+
61
+ * event.category
62
+ * event.tags
63
+ * event.logo
64
+ * ticket.hide (on /ticket_new. You must save the ticket then call /ticket_update to hide)
65
+
66
+ __Documentation errors__
67
+
68
+ * /venue_new and /venue_update does not throw an error if "venue" is invalid/non-unique/empty.
69
+ * Dates are not technically ISO 8601 (ISO 8601 specifies a "T" - not a space - between date and time, so passing perfectly formatted ISO 8601 datetime strings such as those a standard library would provide will cause errors)
70
+ * Error description for event Privacy Error: <pre>"The privacy field must be equal to 0 (public) or 1 (private)"</pre> -> This is the opposite of the actual case. 0 is private and 1 is public, as described in other places within the API.
71
+ * /ticket_new and /ticket_update do not throw errors if quantity not set
72
+
73
+ __Other gotchas__
74
+
75
+ * Venue object included in event has extra "Lat-Long" attribute, along with "latitude" and "longitude". If you're turning a result into an object, this might cause an error if you don't suspect it.
76
+ * Timezones are weird (nothing EventBrite can do about this one): GMT offset for timezones is always computed in standard time (don't adjust for Daylight Savings, unlike UTC offset). This weirdness means that once you save the timezone the Olson description might not match what you think it should (ex. US/Eastern becomes GMT-5 which then becomes America/Bogota)
77
+
78
+ Note on Patches/Pull Requests
79
+ -----------------------------
80
+
81
+ * Fork the project.
82
+ * Make your feature addition or bug fix.
83
+ * Add tests for it. This is important so I don't break it in a
84
+ future version unintentionally.
85
+ * Commit, do not mess with rakefile, version, or history.
86
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
87
+ * Send me a pull request. Bonus points for topic branches.
88
+
89
+ Copyright
90
+ ---------
91
+
92
+ Copyright (c) 2010 David Haslem. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "eventbright"
8
+ gem.summary = %Q{An unofficial gem for EventBrite Integration}
9
+ gem.description = %Q{A simple, unoffical gem that integrates with the EventBrite events service. (http://www.eventbrite.com)}
10
+ gem.email = "therabidbanana@gmail.com"
11
+ gem.homepage = "http://github.com/therabidbanana/eventbright"
12
+ gem.authors = ["David Haslem"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_dependency "httparty", ">= 0.5.2"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :irb
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "eventbright #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
47
+
48
+
49
+ desc "Runs irb with eventbright lib"
50
+ task :irb do
51
+ sh "irb -r 'lib/eventbright'"
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,5 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+ require File.join(libdir, 'eventbright', 'main.rb')
4
+ Dir.glob(File.join(libdir, 'eventbright', '*.rb')).each {|f| require f }
5
+ Dir.glob(File.join(libdir, 'eventbright', 'api_objects', '*.rb')).each {|f| require f }
@@ -0,0 +1,180 @@
1
+ require 'eventbright/api_object_class_methods'
2
+ require 'eventbright/api_object_relationships'
3
+ module EventBright
4
+ class ApiObject
5
+ extend EventBright::ApiObjectClassMethods
6
+ include EventBright::ApiObjectRelationships
7
+ attr_accessor :id, :owner
8
+ attr_accessor :attributes, :relations, :collections
9
+ attr_accessor :dirty, :dirty_relations, :dirty_collections
10
+
11
+ def initialize(owner = false, hash = {})
12
+ preinit
13
+ unless hash.empty?
14
+ @id = hash.delete(:id)
15
+ @owner = owner if owner
16
+ load(hash, true)
17
+ init
18
+ end
19
+ end
20
+
21
+ def preinit
22
+ @attributes = @relations = @collections = {}
23
+ @dirty = @dirty_relations = @dirty_collections = {}
24
+ end
25
+
26
+ def clean!
27
+ @dirty = @dirty_relations = @dirty_collections = {}
28
+ end
29
+
30
+ # Callback after initialization
31
+ def init; end
32
+
33
+ def attribute_get(key); @attributes[key]; end
34
+
35
+ def attribute_set(key, val, no_dirty = false)
36
+ @dirty[key] = true if(@attributes[key] != val && !no_dirty)
37
+ @attributes[key] = val
38
+ after_attribute_set
39
+ end
40
+
41
+ def after_attribute_set
42
+ end
43
+
44
+
45
+ def id=(new_id,*args)
46
+ @id = new_id.to_int
47
+ end
48
+
49
+ def load(hash = {}, no_dirty = false)
50
+ if hash.nil? || hash.size == 0
51
+ response = EventBright.call("#{self.class.singlet_name}_get", prep_api_hash('get'))
52
+ hash = response["#{self.class.singlet_name}"]
53
+ end
54
+ unless hash.nil? || hash.size == 0
55
+ init_with_hash(hash, no_dirty)
56
+ load_relations_with_hash(hash, no_dirty)
57
+ load_collections_with_hash(hash, no_dirty)
58
+ end
59
+ clean! if no_dirty
60
+ after_load(hash)
61
+ end
62
+
63
+
64
+ def init_with_hash(hash, no_dirty = false)
65
+ @attributes ||= {}
66
+ hash.each do |k, v|
67
+ self.__send__("#{k}=", v, no_dirty) unless (self.class.ignores.include?(k) ||
68
+ self.class.ignores.include?(k.to_sym)) ||
69
+ self.class.relations.include?(k) ||
70
+ self.class.relations.include?(k.to_sym) ||
71
+ self.class.collections.include?(k) ||
72
+ self.class.collections.include?(k.to_sym)
73
+ end
74
+ end
75
+
76
+
77
+ # A callback for after loads
78
+ def after_load(hash = {})
79
+ hash
80
+ end
81
+
82
+ # A callback for methods to clean up the hash a bit
83
+ # allowing subclasses to insert the user if necessary
84
+ def prep_api_hash(method = 'get', hash = {})
85
+ hash = hash.merge api_hash
86
+ hash = hash.merge get_hash if method == 'get'
87
+ hash = hash.merge new_hash if method == 'new'
88
+ hash = hash.merge update_hash if method == 'update'
89
+ hash
90
+ end
91
+
92
+ # Callbacks for individual hash changes
93
+ # These are added to the updatable_hash, if appropriate
94
+ # These are called by prep_api_hash
95
+ def api_hash; {:user => owner}; end
96
+ def update_hash; {}; end
97
+ def get_hash; {}; end
98
+ def new_hash; {}; end
99
+
100
+ def nested_hash; {:user => owner, :id => id}; end
101
+
102
+
103
+ # Forces a clean load from the remote API. Load can be passed a hash of local
104
+ # values to avoid an API call, but this circumvents it.
105
+ def load!
106
+ load({}, true)
107
+ end
108
+
109
+
110
+ # Callback that happens before saving. Allows modification of options
111
+ def before_save(opts = {}); opts; end
112
+
113
+ # Save function. Can alter functionality by changing callbacks
114
+ def save(opts = {})
115
+ return false unless dirty?
116
+ opts.merge!(updatable_hash(self.class.requires))
117
+ opts = relations_save(opts)
118
+ opts = before_save(opts)
119
+ call = if loaded?
120
+ c = EventBright.call("#{self.class.singlet_name}_update", prep_api_hash('update', opts))
121
+ after_update
122
+ c
123
+ else
124
+ c = EventBright.call("#{self.class.singlet_name}_new", prep_api_hash('new', opts))
125
+ after_new
126
+ c
127
+ end
128
+ self.id = call["process"]["id"] unless loaded?
129
+ collections_save
130
+ after_save
131
+ clean!
132
+ call
133
+ end
134
+
135
+ # After save callback, only called on a new call
136
+ def after_new; end
137
+ # After save callback, only called on an update call
138
+ def after_update; end
139
+ # After save callback
140
+ def after_save; end
141
+
142
+
143
+ def updatable_hash(always_dirty = [])
144
+ updates = {}
145
+ @attributes.each do |k, v|
146
+ updates[k] = @attributes[k] if @dirty[k] || always_dirty.include?(k)
147
+ end
148
+ updates.merge! :id => @id if @id
149
+ self.class.reformats.each do |k|
150
+ updates[k] = self.__send__(k) if updates[k]
151
+ end
152
+ self.class.renames.each do |k,v|
153
+ updates[v] = updates.delete(k) if updates[k]
154
+ end
155
+ updates
156
+ end
157
+
158
+ def inspect
159
+ "#<#{self.class.to_s}:#{self.id} @attributes=#{@attributes.inspect}>"
160
+ end
161
+
162
+ def to_s
163
+ "#<#{self.class.to_s}:#{self.id} @attributes=#{@attributes.inspect}>"
164
+ end
165
+
166
+ # Defines whether the object has been loaded from a remote source. If not, then
167
+ # we assume it's new when saving.
168
+ def loaded?
169
+ (!@id.nil? || @id == "")
170
+ end
171
+
172
+ # Something is dirty if it's never been loaded or if the @dirty
173
+ # hash contains something.
174
+ def dirty?
175
+ @dirty ||= {}
176
+ @dirty.size > 0 || !loaded?
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,117 @@
1
+ module EventBright
2
+ module ApiObjectClassMethods
3
+ def singlet_name(name = false)
4
+ @singlet_name = name if name
5
+ @singlet_name || self.to_s.gsub('EventBright::', '').downcase
6
+ end
7
+
8
+ def plural_name(name = false)
9
+ @plural_name = name if name
10
+ @plural_name || "#{self.singlet_name}s"
11
+ end
12
+
13
+ def ignores(*args)
14
+ @ignores ||= []
15
+ @ignores.concat(args) unless args.empty?
16
+ @ignores
17
+ end
18
+
19
+ def requires(*args)
20
+ @requires ||= []
21
+ @requires.concat(args) unless args.empty?
22
+ @requires
23
+ end
24
+
25
+ # Columns to reformat when sending outgoing data
26
+ # (Reformatting is assumed to be done by calling the method with the
27
+ # same name as the attribute, so to reformat foo, use def foo... with
28
+ # an attribute_get inside)
29
+ def reformats(*args)
30
+ @reformats ||= []
31
+ @reformats.concat(args) unless args.empty?
32
+ @reformats
33
+ end
34
+
35
+ # Columns to rename when sending outgoing data
36
+ def renames(attrs = false)
37
+ @renames ||= {}
38
+ @renames.merge!(attrs) if attrs
39
+ @renames
40
+ end
41
+
42
+ def updatable(*args)
43
+ args.each{|symbol|
44
+ module_eval( "def #{symbol}(); attribute_get(:#{symbol}); end")
45
+ module_eval( "def #{symbol}=(val, no_dirty = false); attribute_set(:#{symbol}, val, no_dirty); end")
46
+ }
47
+ end
48
+
49
+ def readable(*args)
50
+ args.each{|symbol|
51
+
52
+ module_eval( "def #{symbol}(); attribute_get(:#{symbol}); end")
53
+ module_eval( "def #{symbol}=(val, no_dirty = false); attribute_set(:#{symbol}, val, true); end")
54
+ }
55
+ end
56
+
57
+ def updatable_date(*args)
58
+ args.each{|symbol|
59
+
60
+ module_eval( "def #{symbol}(); EventBright.formatted_time(attribute_get(:#{symbol})); end")
61
+ module_eval( "def #{symbol}=(val, no_dirty = false); attribute_set(:#{symbol}, Time.parse(val), no_dirty); end")
62
+ }
63
+ end
64
+
65
+ def readable_date(*args)
66
+ args.each{|symbol|
67
+
68
+ module_eval( "def #{symbol}(); EventBright.formatted_time(attribute_get(:#{symbol})); end")
69
+ module_eval( "def #{symbol}=(val, no_dirty = false); attribute_set(:#{symbol}, Time.parse(val), true); end")
70
+ }
71
+ end
72
+
73
+ # Columns that are the same as other columns. This is mainly useful
74
+ # for incoming data with inconsistent naming. Args are passed as a hash,
75
+ # where the key is the new method name, and the value is the target method name
76
+ # you are mapping the new one onto. Note that this means there is only the original
77
+ # one stored on the object. Also note this is different from the
78
+ # renames list, which is exclusively for outgoing hashes sent to the API.
79
+ def remap(args = {})
80
+ args.each{|k,v|
81
+ module_eval( "def #{k}(); #{v}; end")
82
+ module_eval( "def #{k}=(val,no_dirty = false); self.__send__('#{v}=', val, no_dirty); end")
83
+ }
84
+ end
85
+
86
+ # Defines a has 1 relation
87
+ def has(args = {})
88
+ @class_relations ||= {}
89
+ args.each{|symbol, klass|
90
+ module_eval( "def #{symbol}(); relation_get(:#{symbol}); end")
91
+ module_eval( "def dirty_#{symbol}!(); relation_dirty!(:#{symbol}); end")
92
+ module_eval( "def dirty_#{symbol}?(); relation_dirty?(:#{symbol}); end")
93
+ module_eval( "def #{symbol}=(val, no_dirty = false); relation_set(:#{symbol}, val, no_dirty); end")
94
+ @class_relations[symbol] = klass
95
+ }
96
+ end
97
+ def relations
98
+ @class_relations || {}
99
+ end
100
+
101
+ # Defines a has may relation
102
+ def collection(args = {})
103
+ @class_collections ||= {}
104
+ args.each{|symbol, klass|
105
+ module_eval( "def #{symbol}(); collection_get(:#{symbol}); end")
106
+ module_eval( "def dirty_#{symbol}!(); collection_dirty!(:#{symbol}); end")
107
+ module_eval( "def dirty_#{symbol}?(); collection_dirty?(:#{symbol}); end")
108
+ module_eval( "def #{symbol}=(val, no_dirty = false); collection_set(:#{symbol}, val, no_dirty); end")
109
+ @class_collections[symbol] = klass
110
+ }
111
+ end
112
+ def collections
113
+ @class_collections || {}
114
+ end
115
+
116
+ end
117
+ end