I have 2 RESTful Rails apps I'm trying to make talk to each other. Both are written in Rails 3 (beta3 at the moment). The requests to the service will require the use an api key which is just a param that needs to be on every request. I can't seem to find any information on how to do this.
You define the url the resource connects to via the site= method. There should be an equivalent query_params= method or similar.
There is one good blog post I found related to this and it's from October 2008, so not exactly useful for Rails 3.
Update: I had a thought. Would a small Rack middleware or Metal be the answer to this? It could just pass through the request, tacking it's api_key on.
Use model#prefix_options which is a hash for passing params into query string (or even as substitions for parts of the Model.prefix, e.g. "/myresource/:param/" will be replaced by the value of prefix_options[:param] . Any hash keys not found in the prefix will be added to the query string, which is what we want in your case).
class Model < ActiveResource::Base
class << self
attr_accessor :api_key
end
def save
prefix_options[:api_key] = self.class.api_key
super
end
end
Model.site = 'http://yoursite/'
Model.api_key = 'xyz123'
m = Model.new(:field_1 => 'value 1')
# hits http://yoursite:80/models.xml?api_key=xyz123
m.save
I recently was faced with a similar issue, if you are on Rails3, it supports using custom header which makes life much easier for these situations.
On the side you are making the request from, add
headers['app_key'] = 'Your_App_Key'
to the class you are inheriting from ActiveResource::Base
On you are server, for Authentication, simply receive it as
request.headers['HTTP_APP_KEY']
For Example:
class Magic < ActiveResource::Base
headers['app_key'] = 'Your_App_Key'
end
now Magic.get, Magic.find, Magic.post will all send the app_key
I have much nicer solution ! I try with Rack in middleware but i no find any solution in this way....
I propose you this module for override methods of ActiveReouse::Base
Add this lib in /lib/active_resource/extend/ directory don't forget uncomment
"config.autoload_paths += %W(#{config.root}/lib)" in config/application.rb
module ActiveResource #:nodoc:
module Extend
module AuthWithApi
module ClassMethods
def element_path_with_auth(*args)
element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
def new_element_path_with_auth(*args)
new_element_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
def collection_path_with_auth(*args)
collection_path_without_auth(*args).concat("?auth_token=#{self.api_key}")
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :auth
alias_method_chain :new_element_path, :auth
alias_method_chain :collection_path, :auth
attr_accessor :api_key
end
end
end
end
end
end
in model
class Post < ActiveResource::Base
include ActiveResource::Extend::AuthWithApi
self.site = "http://application.localhost.com:3000/"
self.format = :json
self.api_key = 'jCxKPj8wJJdOnQJB8ERy'
schema do
string :title
string :content
end
end
Based on Joel Azemar's answer, but I had to make some changes..
First of all, in the active resource gem I used (2.3.8), there is no 'new_element_path', so aliasing that obviously failed..
Second, I updated the way the token is added to the query, because as was, it would break as soon as you add more params yourself. E.g. request for http://example.com/api/v1/clients.xml?vat=0123456789?token=xEIx6fBsxy6sKLJMPVM4 (notice ?token= i.o. &token=)
Here's my updated snippet auth_with_api.rb;
module ActiveResource #:nodoc:
module Extend
module AuthWithApi
module ClassMethods
def element_path_with_auth(id, prefix_options = {}, query_options = nil)
query_options.merge!({:token => self.api_key})
element_path_without_auth(id, prefix_options, query_options)
end
def collection_path_with_auth(prefix_options = {}, query_options = nil)
query_options.merge!({:token => self.api_key})
collection_path_without_auth(prefix_options, query_options)
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :auth
# alias_method_chain :new_element_path, :auth
alias_method_chain :collection_path, :auth
attr_accessor :api_key
end
end
end
end
end
end
An Active Resource currently has no good way of passing an api key to the remote service. Passing api_key as a parameter will add it to the objects attributes on the remote service, I assume that this is not the behaviour you'd except. It certainly wasn't the behaviour I needed
I'd recommend that you have a base class inheriting from ActiveResource::Base and override the self.collection_path and self.element_path class methods to always inject the API KEY something like:
class Base < ActiveResource::Base
def self.collection_path(prefix_options = {}, query_options = {})
super(prefix_options, query_options.merge(api_key: THE_API_KEY))
end
def self.element_path(id, prefix_options = {}, query_options = {})
super(id, prefix_options, query_options.merge(api_key: THE_API_KEY))
end
end
class User < Base; end
User.all # GET /users/?api_key=THE_API_KEY
This will always inject your API_KEY in your ActiveResource method calls.
An Active Resource Object behaves much like a (simplified) Active Record object.
If you wish to pass through a new param, then you can set it on the AR object by adding it as an attribute. eg:
jane = Person.create(:first => 'Jane', :last => 'Doe', :api_key => THE_API_KEY)
it should pass the api_key as a parameter, along with all the others.
Related
I have an ActiveRecord class called User. I'm trying to create a concern called Restrictable which takes in some arguments like this:
class User < ActiveRecord::Base
include Restrictable # Would be nice to not need this line
restrictable except: [:id, :name, :email]
end
I want to then provide an instance method called restricted_data which can perform some operation on those arguments and return some data. Example:
user = User.find(1)
user.restricted_data # Returns all columns except :id, :name, :email
How would I go about doing that?
If I understand your question correctly this is about how to write such a concern, and not about the actual return value of restricted_data. I would implement the concern skeleton as such:
require "active_support/concern"
module Restrictable
extend ActiveSupport::Concern
module ClassMethods
attr_reader :restricted
private
def restrictable(except: []) # Alternatively `options = {}`
#restricted = except # Alternatively `options[:except] || []`
end
end
def restricted_data
"This is forbidden: #{self.class.restricted}"
end
end
Then you can:
class C
include Restrictable
restrictable except: [:this, :that, :the_other]
end
c = C.new
c.restricted_data #=> "This is forbidden: [:this, :that, :the_other]"
That would comply with the interface you designed, but the except key is a bit strange because it's actually restricting those values instead of allowing them.
I'd suggest starting with this blog post: https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns Checkout the second example.
Think of concerns as a module you are mixing in. Not too complicated.
module Restrictable
extend ActiveSupport::Concern
module ClassMethods
def restricted_data(user)
# Do your stuff
end
end
end
I am using a library that is implementing a belongs_to association between two entries in a database. Since this is not the behaviour I need I want to override this method via prepend. But pry tells me that the original method is still called. I double checked and I'm using ruby 2.0.
The code that gets prepended:
module Associations
module ClassMethods
[...]
#Add the attributeName to the belongsToAttributes
#and add a field in the list for the IDs
def belongs_to(attr_name)
#belongsToAttributes ||= []
#belongstoAttributes << attr_name
create_attr attr_name.to_s
attribute belongs_to_string.concat(attr_name.to_s).to_sym
end
def belongsToAttributes
#belongsToAttributes
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
# prepend the extension
Couchbase::Model.send(:prepend, Associations)
I use this in this class:
Note: I also tried to directly override the method in this class but it still doesn't happen
require 'couchbase/model'
class AdServeModel < Couchbase::Model
[...]
#I tried to add the belongs_to method like this
#def belongs_to(attr_name)
# #belongsToAttributes ||= []
# #belongstoAttributes << attr_name
# create_attr attr_name.to_s
# attribute belongs_to_string.concat(attr_name.to_s).to_sym
# end
# def belongsToAttributes
# #belongsToAttributes
# end
end
When I check with pry it shows me that I end up in this method call:
def self.belongs_to(name, options = {})
ref = "#{name}_id"
attribute(ref)
assoc = name.to_s.camelize.constantize
define_method(name) do
assoc.find(self.send(ref))
end
end
Any pointer to what I'm doing wrong would be appreciated.
Edit:
Ok I solved the problem like this:
self.prepended(base)
class << base
prepend ClassMethods
end
end
end
# prepend the extension
Couchbase::Model.send(:prepend, Associations)
Since Arie Shaw's post contains important pointers to solve this problem I will accept his answer. Although he missed the point about extending and prepending the method that I want to call. For a more detailed discussion about my trouble with prepending the methods please refer to this question.
According to the pry trace you posted, the method you wanted to monkey patch is a class method of AdServeModel, not a instance method.
The problem with your Associations module approach is, you are calling Module#prepend to prepend the module to the existing class, however, you wrote a self.included hook method which will only be called when the module is included (not prepended). You should write Module#prepended hook instead.
The problem with the directly overriding approach is, you were actually overriding the instance method, rather than the class method. It should be something like this:
require 'couchbase/model'
class AdServeModel < Couchbase::Model
class << self
# save the original method for future use, if necessary
alias_method :orig_belongs_to, :belongs_to
def belongs_to(attr_name)
#belongsToAttributes ||= []
#belongstoAttributes << attr_name
create_attr attr_name.to_s
attribute belongs_to_string.concat(attr_name.to_s).to_sym
end
def belongsToAttributes
#belongsToAttributes
end
end
end
I'm not sure if macro is even the correct term. Basically, I want to be able to configure ActiveRecord columns easily (using the familiar AR syntax) so that before_save they will always be formatted a certain way by calling an instance method.
I'd like to make all of this accessable from a mixin.
For example:
class MyClass < ActiveRecord::Base
happy_columns :col1, :col2 # I really want this type of convenient syntax
# dynamically created stuff below from a mixin.
before_save :make_col1_happy
before_save :make_col2_happy
def make_col1_happy; self.col1 += " is happy"; end
def make_col2_happy; self.col2 += " is happy"; end
end
try to extend ActiveRecord , a.e.
#in lib/happy_columns.rb
module HappyColumns
def happy_columns(cols)
cols.each do |c|
before_filter "make_#{c}_happy".to_sym
#here you could define your instance methot using define_method
define_method "make_#{c}_happy" do
#your code
end
end
include InstanceMethods
end
module InstanceMethods
#here you could define other your instancemethod
end
end
ActiveRecord::Base.extend HappyColumns
be sure of include the extensions in your load path , then you could use happy_cols in your model.
sorry if there is some mistake , for define_method look at this .
hope this could help.
At the moment I store each option in its own class attribute but this leads to hard to read code when I need to access the passed options from instance methods.
For example if I pass a column name as an option I have to use self.send(self.class.path_finder_column) to get the column value from an instance method.
Notice I have prefixed the class attribute with the name of my plugin to prevent name clashes.
Here is a simple code example of a plugin which is passed an option, column, which is then accessed from the instance method set_path. Can the getters/setters be simplified to be more readable?
# usage: path_find :column => 'path'
module PathFinder
def path_finder(options = {})
send :include, InstanceMethods
# Create class attributes for options
self.cattr_accessor :path_finder_column
self.path_finder_column = options[:column]
module InstanceMethods
def set_path
# setter
self.send(self.class.path_finder_column + '=', 'some value')
# getter
self.send(self.class.path_finder_column)
end
end
end
end
ActiveRecord::Base.send :extend, PathFinder
You can generate all those methods at runtime.
module PathFinder
def path_finder(options = {})
# Create class attributes for options
self.cattr_accessor :path_finder_options
self.path_finder_options = options
class_eval <<-RUBY
def path_finder(value)
self.#{options[:column]} = value
end
def path_finder
self.#{options[:column]}
end
RUBY
end
end
ActiveRecord::Base.send :extend, PathFinder
Unless you need to store the options, you can also delete the lines
self.cattr_accessor :path_finder_options
self.path_finder_options = options
Note that my solution doesn't need a setter and a getter as long as you always use path_finder and path_finder=.
So, the shortest solution is (assuming only the :column option and no other requirements)
module PathFinder
def path_finder(options = {})
# here more logic
# ...
class_eval <<-RUBY
def path_finder(value)
self.#{options[:column]} = value
end
def path_finder
self.#{options[:column]}
end
RUBY
end
end
ActiveRecord::Base.send :extend, PathFinder
This approach is similar to the one adopted by acts_as_list and acts_as_tree.
To start with cattr_accessor creates a class variable for each symbol it's given. In ruby, class variables have their names prefixed with ##.
So you can use ##path_finder_column in place of self.class.path_finder_column.
However that's a moot point considering what I'm going to suggest next.
In the specific case presented by the code in the question. The combination getter and setter you've defined doesn't fit ruby conventions. Seeing as how you're essentially rebranding the accessors generated for the path_finder_column with a generic name, you can reduce it all to just a pair of aliases.
Assuming there's an error in the combo accessor (how is the code supposed to know whether to get or set), The finalized module will look like this:
module PathFinder
def path_finder(options = {})
send :include, InstanceMethods
# Create class attributes for options
self.cattr_accessor :path_finder_column
self.path_finder_column = options[:column]
alias :set_path, path_finder_column
alias :set_path=, "#{path_finder_column}="
end
module InstanceMethods
# other instance methods here.
end
end
You can use cattr_accessor to store the configuration value at a class level and use in all your instance methods. You can see an example at http://github.com/smsohan/acts_as_permalinkable/blob/master/lib/active_record/acts/permalinkable.rb
The code to look at is this:
def acts_as_permalinkable(options = {})
send :cattr_accessor, :permalink_options
self.permalink_options = { :permalink_method => :name, :permalink_field_name => :permalink, :length => 200 }
self.permalink_options.update(options) if options.is_a?(Hash)
send :include, InstanceMethods
send :after_create, :generate_permalink
end
Hope it helps!
I'm developing a ruby on rails app and I want to be able to excecute a method on every AR object before each save.
I thought I'd create a layer-super-type like this:
MyObject << DomainObject << ActiveRecord::Base
and put in DomainObject a callback (before_save) with my special method (which basically strips all tags like "H1" from the string attributes of the object).
The catch is that rails is asking for the domain_object table, which I obviously don't have.
My second attempt was to monkeypatch active record, like this:
module ActiveRecord
class Base
def my_method .... end
end
end
And put that under the lib folder.
This doesnt work, it tells me that my_method is undefined.
Any ideas?
Try using an abstract class for your domain object.
class DomainObject < ActiveRecord::Base
self.abstract_class = true
# your stuff goes here
end
With an abstract class, you are creating a model which cannot have objects (cannot be instantiated) and don't have an associated table.
From reading Rails: Where to put the 'other' files from Strictly Untyped,
Files in lib are not loaded when Rails starts. Rails has overridden both Class.const_missing and Module.const_missing to dynamically load the file based on the class name. In fact, this is exactly how Rails loads your models and controllers.
so placing the file in the lib folder, it will not be run when Rails starts and won't monkey patch ActiveRecord::Base. You could place the file in config/initializers, but I think there are better alternatives.
Another method that I used at a previous job for stripping HTML tags from models is to create a plugin. We stripped a lot more than just HTML tags, but here is the HTML stripping portion:
The initializer (vendor/plugins/stripper/init.rb):
require 'active_record/stripper'
ActiveRecord::Base.class_eval do
include ActiveRecord::Stripper
end
The stripping code (vendor/plugins/stripper/lib/active_record/stripper.rb):
module ActiveRecord
module Stripper
module ClassMethods
def strip_html(*args)
opts = args.extract_options!
self.strip_html_fields = args
before_validation :strip_html
end
end
module InstanceMethods
def strip_html
self.class.strip_html_fields.each{ |field| strip_html_field(field) }
end
private
def strip_html_field(field)
clean_attribute(field, /<\/?[^>]*>/, "")
end
def clean_attribute(field, regex, replacement)
self[field].gsub!(regex, replacement) rescue nil
end
end
def self.included(receiver)
receiver.class_inheritable_accessor :strip_html_fields
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
end
Then in your MyObject class, you can selectively strip html from fields by calling:
class MyObject < ActiveRecord::Base
strip_html :first_attr, :second_attr, :etc
end
The HTML stripping plugin code already given would handle the specific use mentioned in the question. In general, to add the same code to a number of classes, including a module will do this easily without requiring everything to inherit from some common base, or adding any methods to ActiveRecord itself.
module MyBeforeSave
def self.included(base)
base.before_save :before_save_tasks
end
def before_save_tasks
puts "in module before_save tasks"
end
end
class MyModel < ActiveRecord::Base
include MyBeforeSave
end
>> m = MyModel.new
=> #<MyModel id: nil>
>> m.save
in module before_save tasks
=> true
I'd monkeypatch ActiveRecord::Base and put the file in config/initializers:
class ActiveRecord::Base
before_create :some_method
def some_method
end
end