arusarka-dynamic-active-resource 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'active_resource'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'active_resource'
6
+ end
7
+
8
+ $:.unshift File.expand_path(File.dirname(__FILE__))
9
+
10
+ require 'dynamic_active_resource/associations'
11
+ require 'dynamic_active_resource/common_dynamic_class_instance_methods'
12
+ require 'dynamic_active_resource/common_class_methods'
13
+ require 'dynamic_active_resource/helpers'
14
+ require 'dynamic_active_resource/version'
15
+ require 'dynamic_active_resource/base'
16
+
17
+ module DynamicActiveResource
18
+ end
19
+
20
+ # shorter name
21
+ DynAR = DynamicActiveResource
@@ -0,0 +1,21 @@
1
+ require 'dynamic_active_resource/associations/base'
2
+ require 'dynamic_active_resource/associations/has_many'
3
+ require 'dynamic_active_resource/associations/belongs_to'
4
+
5
+ module DynamicActiveResource
6
+ module Associations
7
+ def has_many(assosiation_sym, options = {})
8
+ h_m_association = HasMany.new(assosiation_sym, options)
9
+ associations = self.instance_variable_get(:@associations) || []
10
+ associations << h_m_association
11
+ self.instance_variable_set(:@associations, associations)
12
+ end
13
+
14
+ def belongs_to(association_sym, options = {})
15
+ b_t_association = BelongsTo.new(association_sym, options)
16
+ associations = self.instance_variable_get(:@associations) || []
17
+ associations << b_t_association
18
+ self.instance_variable_set(:@associations, associations)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module DynamicActiveResource
2
+ module Associations
3
+ class Base
4
+ attr_reader :method_name
5
+ def initialize(method_name, options = {})
6
+ @method_name = method_name.to_s
7
+ class_name = options[:through] || @method_name.singularize.camelize
8
+ @associated_class_name = class_name
9
+ end
10
+
11
+ # fetches the resources for the association. requires the caller to be
12
+ # passed through self.
13
+ def resources_for(caller)
14
+ raise 'Not Implemented'
15
+ end
16
+
17
+ private
18
+ def associated_class
19
+ eval(@associated_class_name)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ module DynamicActiveResource
2
+ module Associations
3
+ class BelongsTo < Associations::Base
4
+ def resources_for(caller)
5
+ site_elements = caller.class.site.path.split('/')
6
+ associated_resource_id = site_elements[-1]
7
+ associated_class.find(associated_resource_id)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module DynamicActiveResource
2
+ module Associations
3
+ class HasMany < Base
4
+ def set_associated_class_attributes(caller)
5
+ associated_class_site = File.join(caller.class.site.to_s, caller.class.collection_name, caller.resource_identifier)
6
+ associated_class.site = associated_class_site
7
+ associated_class.user = caller.class.user
8
+ associated_class.password = caller.class.password
9
+ end
10
+
11
+ def resources_for(caller)
12
+ set_associated_class_attributes(caller)
13
+ associated_class.find(:all)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,210 @@
1
+ module DynamicActiveResource
2
+ # ActiveResource makes connecting to rest resources very easy. However it has one problem
3
+ # and a big one at that. If you try setting the authentication credentials or the site or
4
+ # collection name, element name for the class for the second time it doesn't work. E.g.
5
+ #
6
+ # class Person < ActiveResource::Base
7
+ # self.site = 'http://localhost:9090/'
8
+ # end
9
+ #
10
+ # After sometime you change it to
11
+ #
12
+ # Person.site = 'https://org-server/my_proj/'
13
+ # Person.user = 'admin'
14
+ # Person.password = 'secret'
15
+ #
16
+ # Then you do
17
+ #
18
+ # Person.find(:all) => It bombs
19
+ #
20
+ # This class provides a mechanism by which you can get rid of this problem. Extend DynamicActiveResource::Base
21
+ # class in the actual class itself. Do not extend the extended class from ActiveResource::Base.
22
+ #
23
+ # E.g.
24
+ #
25
+ # class Person < DynamicActiveResource::Base
26
+ # end
27
+ #
28
+ # set the credentials
29
+ #
30
+ # Person.site = 'http://localhost:8080'
31
+ # Person.user = 'foo'
32
+ # Person.password = 'bar'
33
+ #
34
+ # Thats it. Now create some objects
35
+ #
36
+ # asur = Person.new(:name => 'Asur', :job => 'fooling around', :status => 'Single and ready 2 mingle')
37
+ # asur.save
38
+ #
39
+ # Now change the class attributes
40
+ #
41
+ # Person.site = 'https://org-server/mingle'
42
+ # Person.collection_name = 'boring_people'
43
+ #
44
+ # Now instantiate an object
45
+ #
46
+ # rakhshas = Person.new(:name => 'Rakhshas', :job => 'eating people', :status => 'just woke up and hungry')
47
+ # rakhshas.save => Voila !!!!!!! it works
48
+ #
49
+ # CUSTOMIZATIONS
50
+ # --------------
51
+ #
52
+ # No amount of wrapping can provide very detailed customizations. Either you have a lot of methods
53
+ # that are not being used or there is hardly anything at all. To oversome this problem this module
54
+ # was written to provide only those methods which are common to most active resource objects.
55
+ # However if you want to have a little more control over your active resource objects its very easy.
56
+ # Here's how you would do it normally
57
+ #
58
+ # class Person < ActiveResource::Base
59
+ # def self.count
60
+ # find(:all).size
61
+ # end
62
+ #
63
+ # def occupation
64
+ # return job if job
65
+ # 'Unemployed'
66
+ # end
67
+ # end
68
+ #
69
+ # To do the same thing, here's how you do it using this library
70
+ #
71
+ # class Person < DynamicActiveResource::Base
72
+ # module DynamicClassSingletonMethods
73
+ # def count
74
+ # find(:all).size
75
+ # end
76
+ # end
77
+ #
78
+ # module DynamicClassInstanceMethods
79
+ # def occupation
80
+ # return job if job
81
+ # 'Unemployed'
82
+ # end
83
+ # end
84
+ # end
85
+ #
86
+ # The DynamicClassInstanceMethods will be available as instance methods in the objects created,
87
+ # DynamicClassSingletonMethods will be available as class methods in the class of the object. Also
88
+ # active resource associations are sometimes paginated. So even if you use find(:all), you get
89
+ # only the first few results. To overcome this, include in your DynamicClassSingletonMethods module
90
+ # a method called find_without_pagination. In that method route it to acitve resource find with
91
+ # proper parameters. An example of the method implementation is
92
+ #
93
+ # module DynamicClassSingletonMethods
94
+ # def find_without_pagination(*args)
95
+ # scope = args.slice!(0)
96
+ # options = args.slice!(0) || {}
97
+ # options[:params] ||= {}
98
+ # options[:params].merge!({:page => 'all'})
99
+ # # call ActiveResource::Base::find with proper options
100
+ # find(scope, options)
101
+ # end
102
+ # end
103
+ #
104
+ # The class level find method will automatically pick up this method if defined as a method in the
105
+ # DynamicClassSingeletonMethod module else it will pass it to find.
106
+ #
107
+ # ASSOCIATIONS
108
+ # ------------
109
+ #
110
+ # This gem also provides active record like associations(highly experimental). Right now it provides only two
111
+ # associations - 1) has_many, 2) belongs_to.
112
+ #
113
+ # 1) has_many
114
+ # -----------
115
+ #
116
+ # Taking the example from above the way it should be done
117
+ #
118
+ # class Person < DynamicActiveResource::Base
119
+ # has_many :cars
120
+ #
121
+ # module DynamicClassSingletonMethods
122
+ # def count
123
+ # find(:all).size
124
+ # end
125
+ # end
126
+ #
127
+ # module DynamicClassInstanceMethods
128
+ # def occupation
129
+ # return job if job
130
+ # 'Unemployed'
131
+ # end
132
+ #
133
+ # def resource_identifier
134
+ # name()
135
+ # end
136
+ # end
137
+ # end
138
+ #
139
+ # Next a car class has to be initialized
140
+ #
141
+ # class Car < DynARBase (alias for DynamicActiveResource::Base, inbuilt in the gem)
142
+ # end
143
+ #
144
+ # set resource options only at the top level( Person in this case)
145
+ #
146
+ # Person.site = 'http://localhost:8080/'
147
+ # Person.user = 'test'
148
+ # Person.password = 'secret'
149
+ #
150
+ # associations take care of setting the site in the children classes automatically
151
+ # you will notice that an additional method resource_identifier() has been defined
152
+ # in the parent class. It would be discussed shortly.
153
+ #
154
+ # The way associations work is if you do something like
155
+ #
156
+ # person = Person.find('asur').cars it will hit the url http://localhost:8080/people/asur/cars.xml
157
+ #
158
+ # You would notice that its getting the cars for the person with name 'asur'. It does so because
159
+ # in the Person class a method called resource_identifier has been defined which says that the id
160
+ # is actually name instead of the database id. If the id attribute in the xml is set appropriately
161
+ # (to 'name' in this case) then you do not need to define the method.
162
+ #
163
+ # 2) belongs_to
164
+ # -------------
165
+ #
166
+ # Again, referring to the example above
167
+ #
168
+ # class Person < DynamicActiveResource::Base
169
+ # has_many :cars
170
+ #
171
+ # module DynamicClassSingletonMethods
172
+ # def count
173
+ # find(:all).size
174
+ # end
175
+ # end
176
+ #
177
+ # module DynamicClassInstanceMethods
178
+ # def occupation
179
+ # return job if job
180
+ # 'Unemployed'
181
+ # end
182
+ #
183
+ # def resource_identifier
184
+ # name()
185
+ # end
186
+ # end
187
+ # end
188
+ #
189
+ # Next a car class has to be initialized
190
+ #
191
+ # class Car < DynARBase (alias for DynamicActiveResource::Base)
192
+ # belongs_to :person
193
+ # end
194
+ #
195
+ # Right now belongs_to supports only assocation with a single class. After defining this you
196
+ # automatically have a method 'person' available.
197
+ #
198
+ # car = Car.find('WB1234I')
199
+ # owner = car.person
200
+
201
+ class Base < ActiveResource::Base
202
+ def self.inherited(subclass)
203
+ subclass.extend(DynamicActiveResource::CommonClassMethods)
204
+ subclass.extend(DynamicActiveResource::Associations)
205
+ end
206
+ end
207
+ end
208
+
209
+ # shorter name
210
+ DynARBase = DynamicActiveResource::Base
@@ -0,0 +1,131 @@
1
+ module DynamicActiveResource
2
+ module CommonClassMethods
3
+ attr_reader :site, :user, :password
4
+
5
+ # creates an object of the class in which this module is extended
6
+ def new(args = {})
7
+ # @resource_class = create_resource_class() # should this be commented
8
+ @resource_class.new(args)
9
+ end
10
+
11
+ # sets the site for the class in which this module is extended
12
+ def site=(site)
13
+ if site != self.site
14
+ @site = site
15
+ uri = URI.parse(site)
16
+ @user = URI.decode(uri.user) if(uri.user)
17
+ @password = URI.decode(uri.password) if(uri.password)
18
+ @resource_class = self.send(:create_resource_class)
19
+ end
20
+ @site
21
+ end
22
+
23
+ # sets the user for the class in which this module is extended
24
+ def user=(user)
25
+ if user != self.user
26
+ @user = user
27
+ @resource_class = self.send(:create_resource_class) if(site)
28
+ end
29
+ @user
30
+ end
31
+
32
+ # sets the password for the class in which this module is extended
33
+ def password=(password)
34
+ if password != self.password
35
+ @password = password
36
+ @resource_class = self.send(:create_resource_class) if(site)
37
+ end
38
+ @password
39
+ end
40
+
41
+ # sets the collection name for the class in which this module is extended
42
+ def collection_name=(collection_name)
43
+ if collection_name != self.collection_name
44
+ @collection_name = collection_name
45
+ end
46
+ @collection_name
47
+ end
48
+
49
+ # sets the elment name for the class in which this module is extended
50
+ def element_name=(element_name)
51
+ if element_name != self.element_name
52
+ @element_name = element_name
53
+ end
54
+ @element_name
55
+ end
56
+
57
+ # collection name for the class in which this module is extended
58
+ def collection_name
59
+ @collection_name || class_name.underscore.pluralize
60
+ end
61
+
62
+ # element name for the class in which this module is extended
63
+ def element_name
64
+ @element_name || class_name.underscore
65
+ end
66
+
67
+ # routes to active resource find
68
+ def find(*args)
69
+ return @resource_class.find_without_pagination(*args) if(@resource_class.respond_to?(:find_without_pagination))
70
+ scope = args.slice!(0)
71
+ options = args.slice!(0) || {}
72
+ obj = @resource_class.find(scope, options)
73
+ obj
74
+ end
75
+
76
+ private
77
+ # creates an active resource class dynamically. All the attributes are set automatically. Avoid calling
78
+ # this method directly
79
+ def create_resource_class
80
+ # raise exceptions if any of site is not set
81
+ raise "Please set the site for #{self} class before using create_resource_class()." unless(self.site)
82
+
83
+ created_class = Class.new(ActiveResource::Base)
84
+
85
+ # set the resource options
86
+ created_class.site = self.site
87
+ created_class.user = self.user
88
+ created_class.password = self.password
89
+ created_class.collection_name = self.collection_name
90
+ created_class.element_name = self.element_name
91
+
92
+ created_class_name = "#{self}::#{class_name}#{Helpers.fast_token()}"
93
+ eval "#{created_class_name} = created_class"
94
+
95
+ # include the common dynamic methods
96
+ created_class.send(:include, DynamicActiveResource::CommonDynamicClassInstanceMethods)
97
+
98
+ # includes a module called InstanceMethods in the class created dynamically
99
+ # if it is defined inside the wrapper class
100
+ inst_meth_mod_name = instance_methods_module_name()
101
+ created_class.send(:include, self.const_get(inst_meth_mod_name.to_sym)) if inst_meth_mod_name
102
+
103
+ # extends the class created dynamically with a module called ClassMethods if
104
+ # it is defined inside the wrapper class
105
+ class_meth_mod_name = class_methods_module_name()
106
+ created_class.extend(self.const_get(class_meth_mod_name)) if class_meth_mod_name
107
+ # put the associations in the created class also
108
+ created_class.instance_variable_set(:@associations, @associations)
109
+
110
+ created_class
111
+ end
112
+
113
+ def class_name
114
+ self.name.split('::')[-1]
115
+ end
116
+
117
+ def instance_methods_module_name
118
+ inst_meth_mod_name = 'DynamicClassInstanceMethods'
119
+ self.constants.detect { |const| const.split('::')[-1] =~ /#{inst_meth_mod_name}/ }
120
+ end
121
+
122
+ def class_methods_module_name
123
+ class_meth_mod_name = 'DynamicClassSingletonMethods'
124
+ self.constants.detect { |const| const.split('::')[-1] =~ /#{class_meth_mod_name}/ }
125
+ end
126
+
127
+ def method_missing(meth_id, *args, &block)
128
+ @resource_class.send(meth_id, *args, &block)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,38 @@
1
+ module DynamicActiveResource
2
+ module CommonDynamicClassInstanceMethods
3
+ def method_missing(method_symbol, *arguments) #:nodoc:
4
+ method_name = method_symbol.to_s
5
+ # dynamic setters and getters
6
+ case method_name.last
7
+ when "="
8
+ return attributes[method_name.first(-1)] = arguments.first
9
+ when "?"
10
+ return attributes[method_name.first(-1)]
11
+ else
12
+ # return attributes
13
+ return attributes[method_name] if(attributes.has_key?(method_name))
14
+ # return the association if it is a association method
15
+ return get_association(method_name) if(association_method?(method_name))
16
+ super
17
+ end
18
+ end
19
+
20
+ def resource_identifier
21
+ id()
22
+ end
23
+
24
+ private
25
+ def association_method?(method_name)
26
+ @associations ||= self.class.instance_variable_get(:@associations)
27
+ return false unless @associations
28
+ association = @associations.detect { |association| association.method_name.to_s == method_name.to_s }
29
+ association ? true : false
30
+ end
31
+
32
+ def get_association(method_name)
33
+ @associations ||= self.class.instance_variable_get(:@associations)
34
+ association = @associations.detect { |association| association.method_name.to_s == method_name.to_s }
35
+ association.resources_for(self)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module DynamicActiveResource
2
+ module Helpers
3
+ def Helpers.fast_token
4
+ values = [
5
+ rand(0x0010000),
6
+ rand(0x0010000),
7
+ rand(0x0010000),
8
+ rand(0x0010000),
9
+ rand(0x0010000),
10
+ rand(0x1000000),
11
+ rand(0x1000000),
12
+ ]
13
+ "%04x%04x%04x%04x%04x%06x%06x" % values
14
+ end
15
+
16
+ def Helpers.encode2html(string)
17
+ html_char_map = {
18
+ '[' => '%5B', ']' => '%5D',
19
+ '(' => '%28', ')' => '%29',
20
+ ',' => '%2C', ' ' => '%20',
21
+ '=' => '%3D', '\'' => '%27',
22
+ '<' => '%3C', '>' => '%3E',
23
+ }
24
+
25
+ string.strip! if string
26
+ encoded_string = ''
27
+ string.each_char do |char|
28
+ encoded_string << (html_char_map[char] || char)
29
+ end
30
+ encoded_string
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ module DynamicActiveResource
2
+ module Version
3
+ Major = '0'
4
+ Minor = '2'
5
+ Tiny = '1'
6
+
7
+ def self.to_s
8
+ [Major, Minor, Tiny].join('.')
9
+ end
10
+ end
11
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arusarka-dynamic-active-resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - asur
@@ -31,6 +31,16 @@ extensions: []
31
31
  extra_rdoc_files:
32
32
  - README
33
33
  files:
34
+ - lib/dynamic_active_resource/associations/base.rb
35
+ - lib/dynamic_active_resource/associations/belongs_to.rb
36
+ - lib/dynamic_active_resource/associations/has_many.rb
37
+ - lib/dynamic_active_resource/associations.rb
38
+ - lib/dynamic_active_resource/base.rb
39
+ - lib/dynamic_active_resource/common_class_methods.rb
40
+ - lib/dynamic_active_resource/common_dynamic_class_instance_methods.rb
41
+ - lib/dynamic_active_resource/helpers.rb
42
+ - lib/dynamic_active_resource/version.rb
43
+ - lib/dynamic_active_resource.rb
34
44
  - README
35
45
  - History.txt
36
46
  - init.rb