Add Facebook metadata to Rails pages with sub-pages - ruby-on-rails

I've tried Facebook's Open Graph protocol in adding meta data on Rails pages. What I want to do now is to make my code not duplicated or D.R.Y.---instead of putting one meta-data header for each controller page I have, I'd like to create a base class called "MyMetaBuilder" which will be inherited by the sub-pages, but don't know where and how to start coding it...
Someone suggested that meta data property values must be dynamically generated depending on the context. For example, PlayMetaBuilder, CookMetaBuilder and so on...
Also, when unit testing the controller action, how do I verify for its existence?
Thanks a lot.

One thing is defining the tags, another is rendering them. I would do the following:
write a controller mixin (something like acts_as_metatagable) where I would define specific fields for each controller (and populate the remaining with defaults). These would be assigned to a class (or instance) variable and in this way be made accessible in the rendering step).
write an helper function which would take all my tags and turn them into html. This helper function would then be called in the layout and be rendered in the head of the document.
so, it would look a bit like this:
# homepage_controller.rb
class HomepageController < ActionController::Base
# option 1.2: include it directly here with the line below
# include ActsAsMetatagable
acts_as_metatagable :title => "Title", :url => homepage_url
end
# lib/acts_as_metatagable.rb
module ActsAsMetatagable
module MetatagableMethods
#option 2.2: insert og_tags method here and declare it as helper method
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
def self.included(base)
base.helper_method :og_tags
end
end
def acts_as_metagabable(*args)
include MetatagableMethods
# insert dirty work here
end
end
# option 1.1: include it in an initializer
# initializers/acts_as_metatagable.rb
ActiveController::Base.send :include, ActsAsMetatagable
# option 2.1: insert og_metatags helper method in an helper
module ApplicationHelper
def og_metatags
#og_tags.map do |k, v|
# render meta tags here according to its spec
end
end
end

What I did for Scoutzie, was put all metadata into a head partial, with if/else cases as such:
%meta{:type => 'Author', :content => "Kirill Zubovsky"}
%meta{'property' => "og:site_name", :content=>"Scoutzie"}
-if #designer
...
-elsif #design
...
-else
...
This way, depending on the variables that load, I know which page it is, and thereby know which metadata to include. This might not be an elegant solution, but it works and it's really simple.

Related

Reload dynamically defined helpers

I want to define view helpers in Rails 5.2.0 on runtime (from within some code that lies within my lib folder and / or some initializer) and I came up with this approach so far:
def new_module
Module.new do
def self.create_method(name, &block)
define_method(name, &block)
end
end
end
def define_dynamic_helper(name, &block)
helpers = new_module
helpers.create_method(name, &block)
ActionView::Base.send :include, helpers
end
Now that I can define dynamic modules that get include into ActionView::Base on runtime, I call them e.g. in my controller like this:
define_dynamic_helper("my_helper") do
"some data"
end
And my view uses the helper like this
<%= my_helper %>
But this has a drawback during development: When I remove the line that defines my helper, it is still available but I would expect a MethodMissing error. And as you can guess, this can lead to very complicated situations to debug.
So I got two questions here:
Is it possible to completely remove all dynamic helpers when Rails does a reload during development? Is there some kind of hook I can use?
Is using ActionView::Base.send :include, helpers the right approach for this? Or is there another entry point that I could use (which maybe provides a better reloading approach?)

How to DRY up 2 controllers/models/views that are basically the exact same

An Order has_many AItems and BItems. As you can tell, the items are basically identical but with an important business reason for categorizing them separately. Wondering what's the best strategy to DRY this up. I realize this is a little opinionated... but hoping to get some clear points of view and arguments.
View code
Currently I'm using a partial. Like this:
class AItemsController
def new
end
end
class BItemsController
def new
end
end
# view files layout
> views
> AItems
> new.html.erb
> BItems
> new.html.erb
# routing
get '/AItems/new'
get '/BItems/new'
# code for /views/AItems/new.html.erb
<%= render "layouts/items_new", object: "AItems" %>
# code for /views/BItems/new.html.erb
<%= render "layouts/items_new", object: "BItems" %>
I'm wondering if it'd be easier to get rid of the partial entirely and just do parameters like this:
class AItemsController
def new
end
end
class BItemsController
def new
end
end
# view files layout
> views
> Items
> new.html.erb
# routing
get '/items/new/:type'
# code for /views/Items/new.html.erb
# code from partial evaluating the param[:type] instead of a passed object
Controller code
Currently everything is duplicated... (I haven't made any attempt at DRYing yet) as in it looks like this (very illustrative, the point is to just show that short of the naming conventions literally everything is basically the same):
class AItemsController
def new
#items = AItems.joins(:order).where("orders.status_id IS NULL")
end
def do_something
a_items_params.each do |item_params|
key_var = item_params[:some_attribute]
...
end
end
end
class BItemsController
def new
#items = BItems.joins(:order).where("orders.status_id IS NULL")
end
def do_something
b_items_params.each do |item_params|
key_var = item_params[:some_attribute]
...
end
end
end
I haven't DRYed this yet because I'm a little conflicted as to how. Examples below are illustrative, forgive if the code isn't exact, but hopefully you get the gist.
Solution A: In one way, I could keep the action definitions in each controller, and then have the code within the action pull from a shared concern:
class AItemsController
include SharedCode
def new
shared_new
end
def do_something
shared_do_something
end
end
Solution B: abstract away the action definitions to the shared concern:
class AItemsController
included SharedAction
shared_action("AItems")
end
Solution C: route everything to a singular controller and again use params to differentiate (passed from view)
class ItemsController
def new
item_type = params[:item_type]
end
def do_something
item_type = params[:item_type]
end
end
Model code
This one is a little more cut and dry, and I don't need a ton of feedback here, I will just used shared concerns for key methods/ callback.
Obviously the answer for one will affect the other. For example if everything routes through a single controller, then I'll have a single view with parameters rather than a partial approach. But because the controller has multiple DRYing options, there's still room for debate.
If you've read this far, I will happily take angry comments about how this question is too loosely defined in exchange for at least some thoughts on what you would do. What's more understandable for you if you were taking over my code?
I am trying to learn and the best way to do that is to solicit multiple points of view and pros and cons to weigh out.
Check out the InheritedResources Gem: https://github.com/josevalim/inherited_resources
Inherited Resources speeds up development by making your controllers
inherit all restful actions so you just have to focus on what is
important. It makes your controllers more powerful and cleaner at the
same time.
Or the Responders Gem, a replacement to Inherited Resources: https://github.com/plataformatec/responders
A set of responders modules to dry up your Rails 4.2+ app.

Using Decorator pattern in Rails but can't access view helper

I'm trying to implement Decorators using the learnings from "Rails 4 Patterns" Code School course, but I'm running into trouble as I need a view helper in the Decorator class.
I want my view to have:
<%= #model_decorator.previous %>
Then in the decorator:
def previous
if object.prev_item.nil?
"Previous"
else
link_to("Previous", object)
end
end
The course suggests you make a call to the decorator within your view helper in the view file itself, but that's no good if the logic could output one result with a helper and one without. (i.e. need the output to be a link or not).
I've tried using helpers.link_to but it errors out as not providing the correct information for the url_for option. I've confirmed link_to("Previous", object) works within the view itself.
For Rails 4
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
As for me it`s better to make a decorator for it:
class LinkDecorator
include ActionView::Helpers::UrlHelper
def initialize(label, object)
#label = label
#object = object
end
def show
link_to(label, url_helpers.send("#{object.class.name.underscore}s_path".to_sym, object))
end
def index
link_to(label, url_helpers.send("#{object.class.name}s_path".to_sym))
end
...
private
attr_reader :label, :object
def url_helpers
Rails.application.routes.url_helpers
end
end
Example usage:
LinkDecorator.new(object.name, object).show
If I understand your problem correctly, you essentially want links in a plain old ruby object.
My solution would be this:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.objects_path(object))
# assuming the object is always of one class
If the object is of a different class, than it would be possible to use the .send method to send the correct message to app ie.:
include ActionView::Helpers::UrlHelper
link_to("Previous", Rails.application.routes.url_helpers.send("#{object.class}s_path".downcase.to_sym, object))
# I'd create a function out of that line to make it a bit neater
It sounds like the error thrown by url_for comes from missing the routes and there's a few ways to include those. My solution kinda avoids that problem by using Rails.application.routes.url_helpers. Hope this helps!

Rabl.render: how to use view helper methods?

I'm using Rabl to generate XML output in a rake task:
xml = Rabl.render #listings, 'feeds/listings', :format => :xml
# do stuff with xml
However, I need to use multiple helper methods in the rabl view file referenced, and I keep getting a NoMethodError as I expected from the answer to this question.
I tried using extends and include in the class used by the rake task but I still get the same error on the helper methods:
require "#{Rails.root}/app/helpers/feeds_helper.rb"
class SerializeData
extends FeedsHelper
def perform
xml = Rabl.render #listings, 'feeds/listings', :format => :xml
# do stuff with xml
end
end
My question is: is there any way to use helper methods in rabl view files generated in this way? (or at least in a way that I can still render them as a string in a rake task?) The helper methods are used many, many times to correctly format various data per fixed requirements, so it would be very difficult to remove them entirely.
I ended up with a monkey-patchy solution.
I noticed that the NoMethodFound error came from an instance of the Rabl::Engine class, so I included the needed routing and helper methods in that class and was then able to access them:
require "#{Rails.root}/app/helpers/feeds_helper.rb"
...
class Rabl::Engine
include Rails.application.routes.url_helpers
include FeedsHelper
end
Also note that the URL host needs to be set if using url in addition to path helpers (e.g. root_url and root_path):
Rails.application.routes.default_url_options[:host] = "www.example.com"
I would definitely prefer a non-monkey-patch solution or at least one where helpers could be included as needed depending on the controller of the action rendered. I'll wait to accept this to see if anyone can come up with such an answer.
You can pass in a scope object with the scope parameter. So if you have access to an object with the helper included, like when in the view context, then you can pass that
eg:
<%= Rabl::Renderer.json(object_to_render, 'api/v1/object/show', view_path: 'app/views', scope: self).html_safe%>
So outside of the view context you'd need to pass in a custom object with the helpers included to make this clean.
eg
class RakeScope
include FeedHelper
end
Rabl::Renderer.json(object_to_render, 'api/v1/object/show', view_path: 'app/views', scope: RakeScope.new() )
I've not tried the second option but the first works great.
While not quite the same problem, I had a similar problem accessing helpers from RSpec specs. I created a helper function that creates a scope that you can use to add whatever helpers you need. The following gave me access to the path and url helper methods and something similar should work for Rake.
#spec/support/rabl_helper.rb
def render_rabl(object, options={})
options = {
format: 'json',
view_path: 'app/views',
file: example.example_group.top_level_description,
scope: RablScope.new
}.merge(options)
result = Rabl.render(object, options.delete(:file), options)
options[:format] == 'json' ? JSON.parse(result) : result
end
class RablScope
include Rails.application.routes.url_helpers
end

Is there a way to add a custom folder to the "partials path"?

I have several of my partials in a folder named partials, and I render them into my view using render 'partials/name_of_my_partial' and that's okay.
Anyhow, is it possible to set up things in a way than I could just use render 'name_of_my_partial' and rails automatically check this partials folder?
Right now, I'm having a Missing partial error.
In rails 3.0 this a bit of a challenge, but looking into it I found that in rails 3.1, they've changed how path lookup works, making it much simpler. (I don't know what exact version they changed it though, it may have been much earlier).
In 3.1, this is relatively simple because they've introduced a way to send multiple prefixes to the path lookup. They are retrieved via the instance method _prefixes.
It's easy to tack on an arbitrary prefix to this, for all controllers, by simply overriding it in the base controller (or in a module you include in your base controller, whichever).
So in 3.1.x (where lookup uses multiple prefixes):
class ApplicationController
...
protected
def _prefixes
#_prefixes_with_partials ||= super | %w(partials)
end
end
Prior to this change, a single prefix was used for lookup, which made this a lot more complicated. This may have not been the best way, but I solved this problem in the past by rescuing from missing template errors with an attempt to look up the same path with my "fallback" prefix.
In 3.0.x (where lookup uses a single path prefix)
# in an initializer
module YourAppPaths
extend ActiveSupport::Concern
included do
# override the actionview base class method to wrap its paths with your
# custom pathset class
def self.process_view_paths(value)
value.is_a?(::YourAppPaths::PathSet) ?
value.dup : ::YourAppPaths::PathSet.new(Array.wrap(value))
end
end
class PathSet < ::ActionView::PathSet
# our simple subclass of pathset simply rescues and attempts to
# find the same path under "partials", throwing out the original prefix
def find(path, prefix, *args)
super
rescue ::ActionView::MissingTemplate
super(path, "partials", *args)
end
end
end
ActionView::Base.end(:include, YourAppPaths)
I haven't been able to find other resources than this SO question on this topic, so I'm posting about my efforts here.
In Rails 3.2+ (Also tested in 4.2), one can access and modify lookup_context.prefixes from within a controller action. So, to modify lookup of template & partial prefixes to include another path you can do this:
class MyObjectsController < ApplicationController
def show
# WARNING: Keep reeding for issues with this approach!
unless lookup_context.prefixes.first == "partials"
lookup_context.prefixes.prepend "partials"
end
end
end
This way, if there is a show.html.erb template in the app/views/partials/ folder then it will be rendered. And the same goes for any partials referenced in show.html.erb.
What's going on here?
The method lookup_context returns an object of type ActionView::LookupContext, which is the object responsible to hold all information required to lookup templates in ActionView. Of note, it also gives you access to lookup_context.view_paths, which is not what is being asked for in this question but sounds like it should be.
WARNING
Modification of the lookup_context.prefixes array is cached for all future requests. Therefore, in order to use it problem-free, it's best to make sure we also remove any prefixes we add.
So, is there an easy way to do this?
Sure. For my own projects, I've created a module which I can include in any controller that needs this ability (or just include it in ApplicationController). Here's my code:
# Helper methods for adding ActionView::LookupContext prefixes on including
# controllers. The lookup_context.prefixes collection is persisted on cached
# controllers, so these helpers take care to remove the passed-in prefixes
# after calling the block.
#
# #example Expected Usage:
# around_action only: :show do |_, block|
# prepend_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
# end
#
# around_action only: %i[index edit update] do |_, block|
# append_penultimate_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
# end
module PrefixesHelper
# Prepends the passed in prefixes to the current `lookup_context.prefixes`
# array, calls the block, then removes the prefixes.
#
# #param [Array<String>] prefixes
def prepend_lookup_context_prefixes(*prefixes, &block)
lookup_context.prefixes.prepend(*prefixes)
block.call
remove_lookup_context_prefixes(*prefixes, index: 0)
end
# Sets the penultimate (2nd-to-last) prefixes in the current
# `lookup_context.prefixes` array, calls the block, then removes the prefixes.
#
# #param [Array<String>] prefixes
def append_penultimate_lookup_context_prefixes(*prefixes, &block)
lookup_context.prefixes.insert(-2, *prefixes)
block.call
remove_lookup_context_prefixes(*prefixes.reverse, index: -2)
end
# Removes the passed in prefixes from the current `lookup_context.prefixes`
# array. If index is passed in, then will only remove prefixes found at the
# specified index in the array.
#
# #param [Array<String>] prefixes
# #param [Integer] index
def remove_lookup_context_prefixes(*prefixes, index: nil)
prefixes.each do |prefix|
if index
if lookup_context.prefixes[index] == prefix
lookup_context.prefixes.delete_at(index)
end
else
lookup_context.prefixes.delete(prefix)
end
end
end
end
As mentioned in the comments in this module, the expected usage for this module is to call the methods contained within via an around_filter call in your controller. This way, the module will take care of removing any prefixes it adds after the controller action has yielded.
For example:
around_filter only: :show do |_, block|
prepend_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
end
Or:
around_filter only: %i[index edit update] do |_, block|
append_penultimate_lookup_context_prefixes("my_optional_name_space/my_objects", &block)
end
I've also included the PrefixesHelper module posted here into a gem that I use to add a few nice extensions such as these to my Rails apps. In case you'd like to use it too, see here: https://github.com/pdobb/core_extensions
Note: I've abandoned this approach due to some weird behavior and incompatibility with the multi_fetch_fragments gem
If you'd like to prefix your partials for an STI collection, I've come up with the following monkey patch (for Rails 3.2, Ruby 2.2+).
config/initializers/partial_renderer_prefix.rb
module ActionView
class PartialRenderer
def merge_prefix_into_object_path(prefix, object_path)
# Begin monkey patch
if #options.key?(:prefix)
prefixes = [#options[:prefix]]
return (prefixes << object_path).join("/")
end
# End monkey patch
if prefix.include?(?/) && object_path.include?(?/)
prefixes = []
prefix_array = File.dirname(prefix).split('/')
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
prefix_array.each_with_index do |dir, index|
break if dir == object_path_array[index]
prefixes << dir
end
(prefixes << object_path).join("/")
else
object_path
end
end
end
end
In a view, use it like this:
<%= render #order_items, prefix: 'orders/timeline' %>
Where you have app/views/orders/timeline/product_order_items/_product_order_item.html.erb and app/views/orders/timeline/subscription_order_items/_subscription_order_item.html.erb partials available for each of your STI models.

Resources