I am trying to use ActiveResource to consume xml data from a third party API. I can use the RESTClient app to successfully authenticate and make requests. I coded my app and when I make a request I get a 404 error. I added:
ActiveResource::Base.logger = Logger.new(STDERR)
to my development.rb file and figured out the problem. The API responds with xml data to requests that do NOT end in xml. EG, this works in RESTClient:
https://api.example.com/contacts
but ActiveResource is sending this request instead
https://api.example.com/contacts.xml
Is there anyway "nice" way to strip the extension from the request being generated by ActiveResource?
Thanks
You can exclude the format string from paths with:
class MyModel < ActiveResource::Base
self.include_format_in_path = false
end
You probably need to override the element_path method in your model.
According to the API, the current defintion looks like this:
def element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
"#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
end
Removing the .#{format.extension} part might do what you need.
You can override methods of ActiveResource::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 WithoutExtension
module ClassMethods
def element_path_with_extension(*args)
element_path_without_extension(*args).gsub(/.json|.xml/,'')
end
def new_element_path_with_extension(*args)
new_element_path_without_extension(*args).gsub(/.json|.xml/,'')
end
def collection_path_with_extension(*args)
collection_path_without_extension(*args).gsub(/.json|.xml/,'')
end
end
def self.included(base)
base.class_eval do
extend ClassMethods
class << self
alias_method_chain :element_path, :extension
alias_method_chain :new_element_path, :extension
alias_method_chain :collection_path, :extension
end
end
end
end
end
end
in model
class MyModel < ActiveResource::Base
include ActiveResource::Extend::WithoutExtension
end
It's far simpler to override the _path accessors mentioned in this answer on a class-by-class basis, rather than monkey-patching ActiveResource application-wide which may interfere with other resources or gems which depend on ActiveResource.
Just add the methods directly to your class:
class Contact < ActiveResource::Base
def element_path
super.gsub(/\.xml/, "")
end
def new_element_path
super.gsub(/\.xml/, "")
end
def collection_path
super.gsub(/\.xml/, "")
end
end
If you're accessing multiple RESTful resources within the same API, you should define your own base class where common configuration can reside. This is a far better place for custom _path methods:
# app/models/api/base.rb
class Api::Base < ActiveResource::Base
self.site = "http://crazy-apis.com"
self.username = "..."
self.password = "..."
self.prefix = "/my-api/"
# Strip .xml extension off generated URLs
def element_path
super.gsub(/\.xml/, "")
end
# def new_element_path...
# def collection_path...
end
# app/models/api/contact.rb
class Api::Contact < Api::Base
end
# app/models/api/payment.rb
class Api::Payment < Api::Base
end
# Usage:
Api::Contact.all() # GET http://crazy-apis.com/my-api/contacts
Api::Payment.new().save # POST http://crazy-apis.com/my-api/payments
Related
Currently in my application I have one helper.rb (Helper module is defined in this file) which is included in my controller.rb file like this:
class Controller
before_action :authenticate_user!
include Helper
Problem is that I need to define one more module e.g. Helper2 and I don't know how to include them using if condition and I don't know if even this solution is possible.
example what I want to do:
class Controller
before_action :authenticate_user!
if variable = 1
include Helper
else
include Helper2
end
Thx for answers!
YAGNI.
There are better ways.
The easist way to make the behavior customizable is to just have a set of methods that can be overridden by classes that consume the module:
module Greeter
def initialize(name)
#name = name
end
def salution
"Hello"
end
def hello
"#{salution}!, my name is #{#name}"
end
end
class Person
include Greeter
end
puts Person.new('Bob').hello # Hello!, my name is Bob
class Dog
include Greeter
def salution
"Woof"
end
end
puts Dog.new('Laika').hello # Woof!, my name is Laika
For more complex tasks there is the "macro method" pattern you'll see all over in Ruby:
module Configurable
def self.included(base)
base.extend(ClassMethods)
base.class_eval do
#options ||= {}
end
end
module ClassMethods
def configure(**kwargs)
#options.merge!(kwargs)
end
def options
#options
end
end
end
class Foo
include Configurable
configure(bar: :baz)
end
puts Foo.options.inspect
# {:bar=>:baz}
This is simply a class method that defines class variables / class instance variables, defines methods or whatever you need to be done. For example these very simplefied API clients:
class Client
include HTTParty
format :json
def answers
self.class.get('/answers')
end
end
class StackoverflowClient < Client
base_uri 'https://stackoverflow.com'
end
class SoftwareEngineeringClient < Client
base_uri 'https://softwareengineering.stackexchange.com'
end
I would like to avoid duplicating the setup for multiple mailer previews. What is the best way to clean this up?
class MyMailerPreview < ActionMailer::Preview
def email1
setup
mailer.email1
end
def email2
setup
mailer.email2
end
def email3
setup
mailer.email3
end
end
Here are two possible solutions I found:
There is something called preview_interceptors that are used when generating mailer previews, you could add your own like this:
config/environments/development.rb
config.action_mailer.preview_interceptors = :my_setup
test/mailers/previews/my_setup.rb
class MySetup
def self.previewing_email(message)
message.subject = "New subject"
end
end
test/mailers/previews/user_mailer_preview.rb
class UserMailerPreview < ActionMailer::Preview
include ActionMailer::Previews
register_preview_interceptor :my_setup
def welcome_email
UserMailer.with(user: User.first).welcome_email
end
end
The message parameter is an instance of ActionMailer::Parameterized::MessageDelivery, I am not sure everything you can do with it, but you can set some attributes on the email itself.
I couldn't find much documentation on preview interceptors, but here is a link to how they are used in Rails.
# Previews can also be intercepted in a similar manner as deliveries can be by registering
# a preview interceptor that has a <tt>previewing_email</tt> method:
#
# class CssInlineStyler
# def self.previewing_email(message)
# # inline CSS styles
# end
# end
#
# config.action_mailer.preview_interceptors :css_inline_styler
#
# Note that interceptors need to be registered both with <tt>register_interceptor</tt>
# and <tt>register_preview_interceptor</tt> if they should operate on both sending and
# previewing emails.
I tried to include Rails before_action in the class, but it wouldn't hook the methods in the previewer, so the second option I found is to build your own before_action like this:
module MySetup
def before_action(*names)
UserMailer.instance_methods(false).each do |method|
alias_method "old_#{method}", method
define_method method do
names.each do |name|
send(name)
end
send("old_#{method}")
end
end
end
end
class UserMailerPreview < ActionMailer::Preview
extend MySetup
def welcome_email
UserMailer.with(user: User.first).welcome_email
end
before_action :setup
private
def setup
puts "Setting up"
end
end
Use an initialize method.
Just override the parent initialize method, call super and then run your setup:
class MyMailerPreview < ActionMailer::Preview
def initialize( params = {} )
super( params )
#email_address = "jules#verne.com"
end
def email1
mailer.email1( #email_address )
end
end
You can view the ActionMailer::Preview.new method here as a reference.
Based on my understanding of what you're asking maybe you could add it into one single method that takes the mailer method as a param
class MyMailerPreview < ActionMailer::Preview
def email_for(emailx) # (Pass the method(email1, etc) as an argument where you're calling it
setup
mailer.send(emailx.to_sym) # Call the method param as a method on the mailer
end
end
Would that work for you?
I am attempting to pass thru request data to the Ability model as suggested here:
class ApplicationController < ActionController::Base
#...
private
def current_ability
#current_ability ||= Ability.new(current_user, request.remote_ip)
end
end
and here:
class Ability
include CanCan::Ability
def initialize(user, ip_address=nil)
can :create, Comment unless BLACKLIST_IPS.include? ip_address
end
end
See: https://github.com/ryanb/cancan/wiki/Accessing-request-data
However, I am using ActiveAdmin with the CancanAdapter, and it uses a separate initialize call via:
def initialize_cancan_ability
klass = resource.namespace.cancan_ability_class
klass = klass.constantize if klass.is_a? String
klass.new user
end
See: https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/cancan_adapter.rb
So how/where can I redefine initialize_cancan_ability so that I can pass in request data similar to the current_ability example?
Basically I'm hoping to just replace the last line as such:
klass.new user, request
Thanks.
You can create a file under lib/monkey_patches/active_admin.rb and put your overridden method there:
require 'cancan'
# Add a setting to the application to configure the ability
ActiveAdmin::Application.inheritable_setting :cancan_ability_class, "Ability"
module ActiveAdmin
private
def initialize_cancan_ability
klass = resource.namespace.cancan_ability_class
klass = klass.constantize if klass.is_a? String
klass.new user, request
end
end
end
If you use Devise, you can access the the Ip from the User model user.current_sign_in_ip
I need to override the behavior of the find method of a class from a gem.
This is the code in the gem:
module Youtube
class Display
attr_accessor :base
def find(id, options = {})
detailed = convert_to_number(options.delete(:detailed))
options[:detailed] = detailed unless detailed.nil?
base.send :get, "/get_youtube", options.merge(:youtube_id => id)
end
end
end
How do I override the above find method in my own YoutubeSearch Controller of my Rails Application?
def find(id, options = {})
//Code here
end
Create a .rb file in config/initializers directory with the following code:
Youtube::Display.class_eval do
def find(id, options = {})
# Code here
end
end
I have elaborated such a solution which DOES NOT require the Rails server restart after every code change (unlike all the other's solutions):
1.
Create YoutubeHelper.rb
module YoutubeHelper
include Youtube
def init_youtube_helper
display.class_eval do
def find(id, options = {})
//Code here
end
end
end
end
2.
youtube_search_controller.rb
class YoutubeSearchController < ActionController::Base
include YoutubeHelper
before_action :init_youtube_helper
end
I need some help with virtual attributes. This code works fine but how do I use it inside a plugin. The goal is to add this methods to all classes that uses the plugin.
class Article < ActiveRecord::Base
attr_accessor :title, :permalink
def title
if #title
#title
elsif self.page
self.page.title
else
""
end
end
def permalink
if #permalink
#permalink
elsif self.page
self.page.permalink
else
""
end
end
end
Thanks
You can run the plugin generator to get started.
script/generate plugin acts_as_page
You can then add a module which defines acts_as_page and extends it into all models.
# in plugins/acts_as_page/lib/acts_as_page.rb
module ActsAsPage
def acts_as_page
# ...
end
end
# in plugins/acts_as_page/init.rb
class ActiveRecord::Base
extend ActsAsPage
end
This way the acts_as_page method is available as a class method to all models and you can define any behavior into there. You could do something like this...
module ActsAsPage
def acts_as_page
attr_writer :title, :permalink
include Behavior
end
module Behavior
def title
# ...
end
def permalink
# ...
end
end
end
And then when you call acts_as_page in the model...
class Article < ActiveRecord::Base
acts_as_page
end
It will define the attributes and add the methods. If you need things to be a bit more dynamic (such as if you want the acts_as_page method to take arguments which changes the behavior) try out the solution I present in this Railscasts episode.
It appears that you want a Module for this
# my_methods.rb
module MyMethods
def my_method_a
"Hello"
end
end
The you want to include it into the classes you want to use it for.
class MyClass < ActiveRecord::Base
include MyMethods
end
> m = MyClass.new
> m.my_method_a
=> "Hello!"
Take a look here for more information on mixing in modules. You can put the module wherever in a plugin if you like, just ensure its named correctly so Rails can find it.
Create a module structure like YourPlugin::InstanceMethods and include it this module like this:
module YourPlugin
module InstanceMethods
# your methods
end
end
ActiveRecord::Base.__send__(:include, YourPlugin::InstanceMethods)
You have to use __send__ to make your code Ruby 1.9 compatible. The __send__ line is usually placed at the init.rb file on your plugin root directory.