How to have a gem controller handle multiple arbitrary models? - ruby

I have four models that I allow commenting on by four separate comment controllers. Those four comment controllers do essentially the same thing and vary only slightly.
In an attempt to remove duplication of the four commenting controllers which are essentially all the same, I've created a Rails Engine as a gem to arbitrarily handle commenting on any arbitrary model that I specify in the routes.rb.
So in my routes.rb file I can now use:
comments_on :articles, :by => :users
with comments_on implemented as follows in my gem:
def comments_on(*resources)
options = resources.extract_options!
[snip of some validation code]
topic_model = resources.first.to_s
user_model = options[:by].to_s
# Insert a nested route
Rails.application.routes.draw do
resources topic_model do
resources "comments"
end
end
end
The routes show up in 'rake routes' and requests correctly get routed to my gem's 'CommentsController' but that's where my gem's functionality ends.
What is the best way detect the context in my gem CommentsController so I can process requests specific to how comments_on was called?
More specifically, how would I implement an index action like the following, having it context aware?
def index
#article = Article.find(params[:article_id])
#comments = ArticleComment.find(:all, :conditions => { :article_id => #article.id })
end
Thanks for the help!

You could specify the topic as an extra parameter in your routes:
Rails.application.routes.draw do
resources topic_model do
resources "comments", :topic_model => topic_model.to_s
end
end
Then your controller could be written like this:
def index
#topic = topic
#comments = topic.comments
end
protected
def topic
m = params[:topic_model]
Kernel.const_get(m).find(params["#{m.underscore}_id"])
end
You could move a lot of the logic out of the controller and into the model as well. topic.comments could be a named scope that all of these models should implement.
I've done similar patterns in the past and there's usually an edge-case that breaks this idea down and you end up doing more 'meta' programming than is wise.
I'd recommend making a base controller, then making simplistic controllers that inherit from that, or try to split these common behaviors into modules.

Related

How to fix FriendlyID duplicate content for :id and :slug

FriendlyID is consistently showing duplicate content for both /slug and /1. In other words, the correct page is loading for the friendly slug (/new-york), but it's loading the same content for the old, unfriendly slug (/11).
Here's my current configuration:
#config/routes.rb
resources :groups, path: ''
get 'groups/:id' => redirect("/%{id}")
#app/models/group.rb
class Group < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
#app/controllers/groups_controller.rb
def show
#group = Group.friendly.find(params[:id])
end
As a potential workaround, I've found putting this in my controller does redirect the bad slugs (/11) to the good slugs (/new-york), but it feels wrong for many reasons (routing outside routes.rb, likely unintended consequences, complex solution for a common problem = probably not the right one).
if request.path != group_path(#group)
return redirect_to #group, :status => :moved_permanently
end
What is the right way to make FriendlyID either (1) redirect :id calls to :slug or (2) simply 404 them?
Thanks to this fantastic comment on Medium, I now have a fully functional and very elegant solution which solves my initial problem (duplicate pages with /new-york and /11) as well as allowing two root-level slug structures to coexist.
get '/:id', to: 'groups#show', constraints: proc {|req| FriendlyId::Slug.where(sluggable_type: 'Group').pluck(:slug).include?(req.params[:id])}, as: :group
get '/:id', to: 'custom_pages#show', constraints: proc {|req| FriendlyId::Slug.where(sluggable_type: 'CustomPage').pluck(:slug).include?(req.params[:id])}, as: :custom_page

Split Grape API (non-Rails) into different files

I am writing an API in Grape, but it stands alone, with no Rails or Sinatra or anything. I'd like to split the app.rb file into separate files. I have looked at How to split things up in a grape api app?, but that is with Rails.
I'm not sure how to make this work with modules or classes — I did try subclassing the different files into my big GrapeApp, but that was ugly and I'm not even sure it worked properly. What's the best way to do this?
I currently have versions split by folders (v1, v2, etc) but that is all.
You don't need to subclass from your main app, you can just mount separate Grape::API sub-classes inside the main one. And of course you can define those classes in separate files, and use require to load in all the routes, entities and helpers that you app might need. I have found it useful to create one mini-app per "domain object", and load those in app.rb, which looks like this:
# I put the big list of requires in another file . .
require 'base_requires'
class MyApp < Grape::API
prefix 'api'
version 'v2'
format :json
# Helpers are modules which can have their own files of course
helpers APIAuthorisation
# Each of these routes deals with a particular sort of API object
group( :foo ) { mount APIRoutes::Foo }
group( :bar ) { mount APIRoutes::Bar }
end
I arrange files in folders, fairly arbitrarily:
# Each file here defines a subclass of Grape::API
/routes/foo.rb
# Each file here defines a subclass of Grape::Entity
/entities/foo.rb
# Files here marshal together functions from gems, the model and elsewhere for easy use
/helpers/authorise.rb
I would probably emulate Rails and have a /models/ folder or similar to hold ActiveRecord or DataMapper definitions, but as it happens that is provided for me in a different pattern in my current project.
Most of my routes look very basic, they just call a relevant helper method, then present an entity based on it. E.g. /routes/foo.rb might look like this:
module APIRoutes
class Foo < Grape::API
helpers APIFooHelpers
get :all do
present get_all_users_foos, :with => APIEntity::Foo
end
group "id/:id" do
before do
#foo = Model::Foo.first( :id => params[:id] )
error_if_cannot_access! #foo
end
get do
present #foo, :with => APIEntity::Foo, :type => :full
end
put do
update_foo( #foo, params )
present #foo, :with => APIEntity::Foo, :type => :full
end
delete do
delete_foo #foo
true
end
end # group "id/:id"
end # class Foo
end # module APIRoutes

Is this Ruby class really badly designed?

I'm quite new to OOP and I'm concerned that this class that I've written is really poorly designed. It seems to disobey several principles of OOP:
It doesn't contain its own data, but relies on a yaml file for
values.
Its methods need to be called in a particular order
It has a lot of instance variables and methods
It does work, however. It's robust, but I'll need to modify the source code to add new getter methods every time I add page elements
It's a model of an html document used in an automated test suite. I keep thinking that some of the methods could be put in subclasses, but I'm concerned that I'd have too many classes then.
What do you think?
class BrandFlightsPage < FlightSearchPage
attr_reader :route, :date, :itinerary_type, :no_of_pax,
:no_results_error_container, :submit_button_element
def initialize(browser, page, brand)
super(browser, page)
#Get reference to config file
config_file = File.join(File.dirname(__FILE__), '..', 'config', 'site_config.yml')
#Store hash of config values in local variable
config = YAML.load_file config_file
#brand = brand #brand is specified by the customer in the features file
#Define instance variables from the hash keys
config.each do |k,v|
instance_variable_set("##{k}",v)
end
end
def visit
#browser.goto(#start_url)
end
def set_origin(origin)
self.text_field(#route[:attribute] => #route[:origin]).set origin
end
def set_destination(destination)
self.text_field(#route[:attribute] => #route[:destination]).set destination
end
def set_departure_date(outbound)
self.text_field(#route[:attribute] => #date[:outgoing_date]).set outbound
end
def set_journey_type(type)
if type == "return"
self.radio(#route[:attribute] => #itinerary_type[:single]).set
else
self.radio(#route[:attribute] => #itinerary_type[:return]).set
end
end
def set_return_date(inbound)
self.text_field(#route[:attribute] => #date[:incoming_date]).set inbound
end
def set_number_of_adults(adults)
self.select_list(#route[:attribute] => #no_of_pax[:adults]).select adults
end
def set_no_of_children(children)
self.select_list(#route[:attribute] => #no_of_pax[:children]).select children
end
def set_no_of_seniors(seniors)
self.select_list(#route[:attribute] => #no_of_adults[:seniors]).select seniors
end
def no_flights_found_message
#browser.div(#no_results_error_container[:attribute] => #no_results_error_container[:error_element]).text
raise UserErrorNotDisplayed, "Expected user error message not displayed" unless divFlightResultErrTitle.exists?
end
def submit_search
self.link(#submit_button_element[:attribute] => #submit_button_element[:button_element]).click
end
end
If this class is designed as a Facade, then it's not (too) bad design. It provides a coherent unified way to perform related operations that rely on a variety of un-related behavior holders.
It appears to be poor separation of concerns, in that this class essentially coupling all the various implementation details, which might turn out to be somewhat tricky to maintain.
Finally, the fact methods need to be called in a specific order may hint at the fact you're trying to model a state machine - in which case it probably should be broken down to several classes (one per "state"). I don't think there's a "too many methods" or "too many classes" point you'd reach, the fact is you need the features provided by each class to be coherent and making sense. Where to draw the line is up to you and your specific implementation's domain requirements.

Rails current_path Helper?

I'm working on a Rails 3.2 application with the following routing conditions:
scope "(:locale)", locale: /de|en/ do
resources :categories, only: [:index, :show]
get "newest/index", as: :newest
end
I've a controller with the following:
class LocaleController < ApplicationController
def set
session[:locale_override] = params[:locale]
redirect_to params[:return_to]
end
end
I'm using this with something like this in the templates:
= link_to set_locale_path(locale: :de, return_to: current_path(locale: :de)) do
= image_tag 'de.png', style: 'vertical-align: middle'
= t('.languages.german')
I'm wondering why there doesn't exist a helper in Rails such as current_path, something which is able to infer what route we are currently using, and re-route to it include new options.
The problem I have is using something like redirect_to :back, one pushes the user back to /en/........ (or /de/...) which makes for a crappy experience.
Until now I was storing the locale in the session, but this won't work for Google, and other indexing services.
I'm sure if I invested enough time I could some up with something that was smart enough to detect which route matched, and swap out the locale part, but I feel like this would be a hack.
I'm open to all thoughts, but this SO question suggests just using sub(); unfortunately with such short and frequently occurring strings as locale short codes, probably isn't too wise.
If you are using the :locale scope, you can use url_for as current_path:
# Given your page is /en/category/newest with :locale set to 'en' by scope
url_for(:locale => :en) # => /en/category/newest
url_for(:locale => :de) # => /de/kategorie/neueste
In case somebody looks here, you can use request.fullpath which should give you all after domain name and therefore, will include locale.

Rails routing: Giving default values for path helpers

Is there some way to provide a default value to the url/path helpers?
I have an optional scope wrapping around all of my routes:
#config/routes.rb
Foo::Application.routes.draw do
scope "(:current_brand)", :constraints => { :current_brand => /(foo)|(bar)/ } do
# ... all other routes go here
end
end
I want users to be able to access the site using these URLs:
/foo/some-place
/bar/some-place
/some-place
For convenience, I'm setting up a #current_brand in my ApplicationController:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_filter :set_brand
def set_brand
if params.has_key?(:current_brand)
#current_brand = Brand.find_by_slug(params[:current_brand])
else
#current_brand = Brand.find_by_slug('blah')
end
end
end
So far so good, but now I must modify all *_path and *_url calls to include the :current_brand parameter, even though it is optional. This is really ugly, IMO.
Is there some way I can make the path helpers automagically pick up on #current_brand?
Or perhaps a better way to define the scope in routes.rb?
I think you will want to do something like this:
class ApplicationController < ActionController::Base
def url_options
{ :current_brand => #current_brand }.merge(super)
end
end
This method is called automatically every time url is constructed and it's result is merged into the parameters.
For more info on this, look at: default_url_options and rails 3
In addition to CMW's answer, to get it to work with rspec, I added this hack in spec/support/default_url_options.rb
ActionDispatch::Routing::RouteSet.class_eval do
undef_method :default_url_options
def default_url_options(options={})
{ :current_brand => default_brand }
end
end

Resources