Generics Example Question - ruby-on-rails

I am fairly new to Ruby on Rails and as a C# developer, when I want to re-use code (for a repository class), I could put it into a base class of type <T> to be able to do something like this:
public virtual IEnumerable<T> GetAll()
{
return Context<T>.GetAll();
}
If I need to do any custom logic, I could, of course, override the method in my 'User' repository.
In Ruby, I am familiar that you can do this:
class UsersController < ApplicationController
This will allow access to all methods in ApplicationController and it's parent classes. When using scaffolding, it generates the following method in each of my child classes:
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
What I end up with is 10 classes that have the same method, but the only difference is 'User.all', 'Post.all', etc.
How would I make this method generic so I can put it in my ApplicationController class?
Thanks for any assistance you can provide to a Ruby on Rails newbie.

The first thing to realize about the scaffolding code is that it can be abreviated, as such:
def index
#users = User.all
end
unless you intend to deliver the view in another format, like json, html, pdf, the respond_to block is unnecessary. If you still feel the need to dry up this method, you could do something like
# app/controllers/concerns/autoload_records.rb
module AutoloadRecords
included do
before_action :load_records, only: :index
before_action :load_record, only: [:create, :show, :edit, :update, :destroy]
end
private
def load_records
#records = model_class.all
end
def load_record
#record = model_class.find(params[:id])
end
def model_class
klass = self.class.to_s[/\A(\w+)sController\Z/,1] #=> get the name of the class from the controller Constant
Object.const_get(klass)
end
end
and write your controller like
class UsersController < ApplicationController
include AutoloadRecords
def index
#records # => #<ActiveRecord::Relation[...]>
end
def show
#record # => #<User ...>
end
def non_rest_action
#record # => nil
#records # => nil
end
end

Rather than doing an eval where you really don't want to be doing one. Check out Jose Valim's Inherited Resources gem. It provides the standard CRUD methods for all of your controllers and is quite sophisticated. It is also thoroughly tested so you don't have to worry about making sure your generic code operates as expected in all cases.
For details on how to use it see the GitHub page linked.

Maybe a simple solution could be to rely on mixins.
You define a module,
module MyModule
def my_index(klass)
#elements = klass.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #elements }
end
end
end
Then, you have in your controller,
include MyModule
def index
my_index(User)
end
Of course, you need to use #elements in your views. If you want a different variable name in each view you can do
def my_index(klass, var_name)
self.instance_variable_set(var_name, klass.all)
...
end

There are several rails plugins that help to reduce this kind of duplication. This one was covered in railscast episode 230.
https://github.com/josevalim/inherited_resources

Based on my experience, you rarely end up with 10 index action looking like #user = User.all. If you know in advance that some actions between different models will be identical - well then may be it makes sense to extract common logic. But then again may be these models are somehow connected? I wouldn't say in advance that Post and User will have identical index actions.
For a short method like this I wouldn't try to eleminate repetition because you may end up losing readability.

Related

Rails 4, actions caching, why actions still runs, even when cached

I have two implementation of one simple controller.
In first implementation every thing works fine, action show executing only when cache missing.
But I understand that set object #page in Proc of cache action it is a bad idea.
That's why I have second implementation, which looks much better.
It works too and returns cached view.
BUT, I can't understand why when I use before_filter, the action show is still executing even when cache hit. In log I see current time.
Could you explain me why?
please.
Implementation 1
class Frontend::StaticPagesController < Frontend::FrontendController
caches_action :show, :cache_path => Proc.new {
#page = StaticPage.find_in_cache(params[:permalink])
{key: "#{#page.cache_key}-#{I18n.locale}"}
}
def show
logger.debug Time.now.to_s.yellow
end
end
Implementation 2
class Frontend::StaticPagesController < Frontend::FrontendController
before_filter :set_page, :show
caches_action :show, :cache_path => Proc.new {
{key: "#{#page.cache_key}-#{I18n.locale}"}
}
def show
logger.debug Time.now.to_s.yellow
end
def set_page
#page = StaticPage.find_in_cache(params[:permalink])
end
end
P.S. Rails '4.2.3'
This line
before_filter :set_page, :show
Defines :show as a filter. This is why it runs.
My guess is that you want to define the :set_page filter to run only for show action. In this is indeed your intention, use:
before_filter :set_page, only: :show
PS: _filter filters are deprecated. Use _action filters instead, like before_action.

How can I customize the way Rails 4 render() finds files?

I'm serving a versioned web service from Rails.
I would very much like to be able to call render like normal:
render 'index'
And have it correctly serve the requested version from the following:
index.v1.json.jbuilder
index.v2.json.jbuilder
index.v3.json.jbuilder
Assuming that I already know the requested version within the context of the controller action execution, how do I get render() to leverage it?
I have used the versioncake gem
You should definitely check this out. File name will be very close to what you have:
index.v1.json.jbuilder
would be
index.json.v1.jbuilder
Sounds like a builder design pattern might work here. Have a view builder object that returns the desired behavior.
module ViewBuiler
def build(name, api_version)
View.new(name, api_version).build
end
class View < Struct(:name, :api_version)
def build
[name, api_version, 'json', 'jbuilder'].join('.')
end
end
end
and in your controller you could just do something like:
ApplicationController
include ViewBuilder
end
MyController < ApplicationController
def index
...
# you can pass either strings or symbols to build and it will
# return 'index.v1.json.jbuilder'
render build(:index, params[:api_version])
end
end
And disclaimer, this is just an idea and not something I've implemented. I like the approach for 2 reason. The Controller actions remain skinny and don't have logic in it. A builder like this seems pretty easy to test. And it keeps the thing that might change (views etc) isolated into something that can change frequently as long as it retains it's interface that the Controllers will work with.
This seems like a candidate for Variants. This is new to 4.1 though.
class MyController < ActionController::Base
before_action :set_variant
def my_action
.....
respond_to do |format|
format.json do |json|
json.v1 do
# render whatever you need here
end
end
end
end
protected
def set_variant
request.variant = :v1 if request.params[:version] == "v1"
....
end
end

Ruby/Rails: trying to invent an inherited resource gem. Class instance variable in mixin

I want to DRY my views and controllers from a lot of similar code. I want to do it by myself in educational purpose, I know about the InheritedResourse gem.
So far I wrote:
class Admin::ResourcesController < Admin::AdminBaseController
before_filter :get_model_name
def index
result = #model.all #Resource.all
instance_variable_set "##{##collection_resource_name}", result # #resources = result
result # return it duo it can be used with super
end
def show
result = #model.find(params[:id])
instance_variable_set "##{##instance_resource_name}", result
result
end
protected
##collection_resource_name = 'resources'
##instance_resource_name = 'resource'
def self.set_resource_name(hash)
##instance_resource_name = hash[:instance_resource_name]
##collection_resource_name = hash[:collection_resource_name]
end
private
def get_model_name
#model = controller_name.classify.constantize # Resource
end
end
Only two actions, but you got the idea: abstract any model to a 'resource', set a list of model fields (or get it dynamicaly) and that's it.
First of all, I think instead of ##instance_resource_name (class variable), I need a class instance variable. I'm right?
... but, it is not a main question. I think that it's cool when this kind of code is wrapped in mixin. Because in my example it is Admin::ResourceController, but I can also have a User::ResourceController or just something another.
Ok, I wrapped it in a mixin. For usability, I want to call something like actions only: [:index, :show] in controller in section, where I put before_filter, for example.
How this section of code is called? Class instance?
Ok, the example:
require 'active_support/concern'
module ScaffoldResources
extend ActiveSupport::Concern
included do
def hello
self.class.action_list
end
end
module ClassMethods
#action_list = [:new, :show, :index, :edit, :update, :destroy, :create]
attr_accessor :actions_list
def actions(*params)
#actions_list = params
end
end
end
For testing, I create this small controller:
class Admin::UsersController < Admin::ResourcesController
include ScaffoldResources
#actions_list = 'hard set'
actions 'some','actions','i think'
def show
render json: hello
end
end
So, when I call hello method (it do only self.class.action_list) I want to see anything. I set class instance variable in mixin and in class – hardcode and through method defined in mixin. But it's nil!
I think you got the idea, what I'm trying to achieve. How it can be achieved?

Rails 3.2 respond_with

The respond_with accepts some parameters, e.g. respond_with(#resource, methods: [:method])
These options should be used in every action. So instead of putting it into every method by hand, is there a possibility to set some default options for just this controller?
The easy and customizable way to do this is by creating a new response method that wraps responds_with.
For example:
class ResourcesController < ApplicationController
def index
#resources = Resource.all
custom_respond_with #resources
end
private
def custom_respond_with(data, options={})
options.reverse_merge!({
# Put your default options here
:methods => [ :method ],
:callback => params[:callback]
})
respond_with data, options
end
end
You could, of course, also overwrite the respond_with completely, however, I find it to be much clearer in the code if you change the name of the method. It also will allow you to use a custom_respond_with in most actions but the standard respond_with in one or two if necessary.
Taking this one step further, if you move the custom_respond_with method to the ApplicationController, you can use it in all of your controllers as necessary.
If you want to specify different default options on a per controller basis, you can do so easily:
class ResourcesController < ApplicationController
def index
custom_respond_with Resource.all
end
private
def custom_respond_options
{ :methods => [ :method ] }
end
end
class ApplicationController < ActionController::Base
protected
def default_custom_respond_options
{}
end
def custom_respond_with(data, options={})
options.reverse_merge! default_custom_respond_options
respond_with data, options
end
end

attributes and constructors in rails

I'm new to rails and don't even know if this is the correct way of solving my situation.
I have a "Club" ActiveRecords model which has a "has_many" association to a "Member" model. I want the logged in "Club" to only be able to administrate it's own "Member" so in the beginning of each action in the "Member" model I did something similar to the following:
def index
#members = Club.find(session[:club_id]).members
to access the right members. This did not however turn out very DRY as I did the same in every action. So I thought of using something equivalent to what would be called a constructor in other languages. The initialize method as I've understood it. This was however not working, this told me why, and proposed an alternative. The after_initialize.
def after_initialize
#club = Club.find(session[:club_id])
end
def index
#members = #club.members
....
does not seem to work anyway. Any pointers to why?
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.members
Makes me think that the #club var isn't set at all.
Also, is this solution really a good one? This makes it hard to implement any kind of "super admin" who can manage the members in all of the clubs. Any ideas on where I am missing something?
You can use a before_filter.
Define the filter in your ApplicationController (so that you can access it from any controller).
class ApplicationController < ActionController::Base
# ..
protected
def load_members
#members = if session[:club_id]
Club.find(session[:club_id]).members
else
[]
end
end
end
Then, load the filter before any action where you need it.
For example
class ClubController < ApplicationController
before_filter :load_members, :only => %w( index )
def index
# here #members is set
end
end
Otherwise, use lazy loading. You can use the same load_members and call it whenever you need it.
class ClubController < ApplicationController
def index
# do something with members
load_members.each { ... }
end
end
Of course, you can customize load_member to raise an exception, redirect the client if #members.empty? or do whatever you want.
You want to use a before_filter for this.
class MembersController < ApplicationController
before_filter :find_club
def index
#members = #club.members
end
private
def find_club
#club = Club.find(session[:club_id])
end
end
I'm a fan of a plugin called Rolerequirement. It allows you to make custom roles and apply them by controller: http://code.google.com/p/rolerequirement/

Resources