Friendly_id and Active Admin conflict - possibly because of revert_freindly_id - ruby-on-rails

first time asking a question on stack overflow :)
I'm having a conflict between friendly_id and active admin (it's an assumption), as discussed in many threads here. I've looked at all those threads, but I'm not entirely sure they solve my problem. Sorry for the really long post!
I'm trying to create friendly links to products on my website. I've added the friendly_id gem and everything works fine in my dev and staging environments, but friendly links fail on production. Here is all my code:
Model:
class Product < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
...
end
Controller:
class ProductsController < ApplicationController
before_filter :get_product, only: [:show]
...
private
def get_product
#product = Product.friendly.find(params[:id])
end
end
All my product records have a completed slug field at this point. I don't want to use slugs in my admin interface, so when I came across a solution here, I went ahead and modified it a bit to get active admin to work together with friendly_id.
config/initializers/active_admin.rb:
ActiveAdmin.setup do |config|
...
config.before_filter :revert_friendly_id
end
I've defined revert_friendly_id in the application controller:
class ApplicationController < ActionController::Base
...
protected
def revert_friendly_id
model_name = self.class.name.match(/::(.*)Controller$/)[1].singularize
# Will throw a NameError if the class does not exist
Module.const_get model_name
eval(model_name).class_eval do
def to_param
id.to_s
end
end
rescue NameError
end
end
I've noticed that when I first deploy to production via capistrano, the friendly links work as expected. So my product links are accessible with: http://website.com/products/my-product-slug. But the minute I access the admin interface on production, the links immediately switch back to product ids instead: http://website.com/products/12345. I'm not entirely sure how to resolve this problem, though I understand why it might be happening, can someone help me please?

Here is how I solved the problem. Based on armstrjare's fix at this link.
I removed the revert_friendly_id function from my application controller and the before_filter from my config. Then just added the following to app/admin/product.rb:
ActiveAdmin.register Product do
around_filter do |controller, action|
Product.class_eval do
alias :__active_admin_to_param :to_param
def to_param() id.to_s end
end
begin
action.call
ensure
Product.class_eval do
alias :to_param :__active_admin_to_param
end
end
end
...
end
And everything worked as expected. Hope this helps someone else!

I found a very simple solution: Just overwrite the to_param in your model and check if it is called from active_admin.
app/models/product.rb:
class Product < ActiveRecord::Base
def to_param
if caller.to_s.include?"active_admin"
id && id.to_s
else
slug
end
end
end

When you set the to_param method, it will be set on the entire application. So you have to check if the requested controller is in the Admin namespace or not. Based on that you have to switch back the return of the to_param method.

You can redefine find_resource method in controller:
controller do
def find_resource
scoped_collection.friendly.find(params[:id])
end
end
For the active_admin belongs_to association you can to use the finder: option (from https://github.com/activeadmin/inherited_resources/blob/master/lib/inherited_resources/belongs_to_helpers.rb#L17)
For example:
belongs_to :content, finder: :find_by_slug!

Related

The correct way to do inheritance in Rails mountable engine

Today, I found a very strange issue while writing a mountable engine using Rails 3.
I have the following ApplicationController in my engine:
module Marketplace
class ApplicationController < ::ApplicationController
before_filter :merge_abilities
layout 'marketplace/application'
def marketplace_current_user
Marketplace::User.find(current_user.id)
end
private
def merge_abilities
current_ability.merge(Ability.new(current_user))
end
end
end
And my User model definition is
module Marketplace
class User < ::User
devise omniauth_providers: [:facebook, :paypal]
has_one :payout_identity, class_name: "Marketplace::PayoutIdentity"
has_many :course_purchases, class_name: "Marketplace::CoursePurchase"
def has_verified_payout_identity?
self.payout_identity and self.payout_identity.receiver_id
end
end
end
After starting up the rails server, the first request to load a page will have the controller run the marketplace_current_user method correctly and load the engine's User class. However any request after the first one will given a strange NameError - "uninitialized constant Marketplace::Marketplace::User".
I tried removing the namespace in marketplace_current_user definition but it will load the main app's User class instead.
At last when I change my ApplicationController to look like this:
class Marketplace::ApplicationController < ::ApplicationController
before_filter :merge_abilities
layout 'marketplace/application'
def marketplace_current_user
Marketplace::User.find(current_user.id)
end
private
def merge_abilities
current_ability.merge(Ability.new(current_user))
end
end
Everything would work fine.
Could someone enlighten me where I got it wrong at the beginning? Was it wrong to do inheritance that way?

ActiveAdmin with friendly id

I am using friendly_id in my rails 4 application with slug. Now I am using active_admin gem.
Problem:
When I click on show link from active admin for Group resource, It is throwing the following exception:
ActiveRecord::RecordNotFound at /admin/groups/username20-s-group-1
I guess, I need to override some of the active_admin default functions?
There are cases, when application has quit a few resources, hence in order to keep it DRY there is a nice solution requiring few lines of code for whole application - simply override activeadmin's resource controller.
Create config/initializers/active_admin_monkey_patching.rb file with the following content:
ActiveAdmin::ResourceController.class_eval do
def find_resource
finder = resource_class.is_a?(FriendlyId) ? :slug : :id
scoped_collection.find_by(finder => params[:id])
end
end
Do not forget to restart the server.
A better approach to #AndreyDeineko's is to override ActiveAdmin::ResourceController's find_resource method in config/initialisers/active_admin.rb and leverage the methods provided by FriendlyId (5.x at this point):
In config/initialisers/active_admin.rb:
ActiveAdmin.setup do |config|
# == Friendly Id addon
ActiveAdmin::ResourceController.class_eval do
def find_resource
if resource_class.is_a?(FriendlyId)
scoped_collection.friendly.find(params[:id])
else
scoped_collection.find(params[:id])
end
end
end
# initial config
end
This looks much cleaner to me, than putting it in the application controller, as it is related to the configuration of Active Admin.
Found solution for the problem:
In your app/admin/[ResourceName.rb] add:
# app/admin/group.rb
# find record with slug(friendly_id)
controller do
def find_resource
begin
scoped_collection.where(slug: params[:id]).first!
rescue ActiveRecord::RecordNotFound
scoped_collection.find(params[:id])
end
end
end
This solved my problem.
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :username, :use => [:slugged, :finders]
IMHO, it's suboptimal to completely override the find_resource as most of the answers suggest. Better to prepend a module and call super preserving normal behaviour when FriendlyId is not in use. For reference you can check how this method is currently (as of writing) implemented, it is not simply scoped_collection.find(params[:id]) as one might think:
https://github.com/activeadmin/activeadmin/blob/b45b1fb05af9a7f6c5e2be94f61cf4a5f60ff3bb/lib/active_admin/resource_controller/data_access.rb#L104
module ActiveAdminFriendlyIdScoping
def find_resource
if resource_class.is_a? FriendlyId
scoped_collection.friendly.find params[:id]
# Or potentially even
# scoped_collection.friendly.send method_for_find, params[:id]
# Or you could do something like this
# raise "Using FriendlyId, find method configuration ignored" if method_for_find != :find
else
super
end
end
end
ActiveAdmin.setup do |config|
#...
Rails.application.config.to_prepare do
ActiveAdmin::ResourceController.prepend(ActiveAdminFriendlyIdScoping)
end
end
If you've tried some of the other answers here and gotten
uninitialized constant InheritedResources::Base (NameError)
Then you might consider monkey patching FriendlyId rather than ActiveAdmin. Create a new initializer file config/initializers/friendly_id_monkey_patch.rb containing this:
module FriendlyIdModelMonkeyPatch
def to_param
if caller.to_s.include? 'active_admin'
id&.to_s
else
super
end
end
end
module FriendlyId::Model
prepend FriendlyIdModelMonkeyPatch
end
Now all of your FriendlyId models will revert to using their ID in ActiveAdmin and their slug everywhere else.
See also this answer, which does the same thing but for only one model (rather than monkey patching for all FriendlyId models)

How can I make a condition with current_user in controller

I'm a freshman to learn Rails and working on my first project about online book writing.
I've already made the MVC of user,book and section. I wanna create a button called "Author Place",which can show all the pieces written by the current logged in user.
I wanna ask a simple question. How can I make a condition with the current username to select the current author's works from the book database. Should I put this code in controller or view?
Code as follow.
current_user method of the ApplicationController:
protect_from_forgery
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
The Section model :
class Section < ActiveRecord::Base
attr_accessible :book_id, :section_content, :section_tag, :user_username
belongs_to :book
belongs_to :user
end
The Section controller :
class SectionsController < ApplicationController
def userpieces
#sections=Section.find(:all, :conditions=>"user_username=current_user.username") # This part doesn't work
end
end
Or any suggestions with some other way to do this?
Assuming you have a corresponding has_many :sections association in your User model, try this:
#sections = current_user.sections
As depa and izuriel mentioned, you should be able to get it simply if your model relation is correctly set.
Anyway, if you wish to get it in the way you try please use:
#sections=Section.find(:all, :conditions => ["user_username= ?",current_user.username])
Please note, in rails 3, .find(:all is deprecated, please use .all instead.

ActiveRecord::ReadOnlyRecord when using ActiveAdmin and Friendly_id

I started using ActiveAdmin recently in a project and almost everything works great but I'm having a problem when using it in combination with the friendly_id gem. I'm getting ActiveRecord::ReadOnlyRecord thrown for my forms [i believe] because of the friendly_id attribute whose ID is readonly:
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"Rc5PmUYZt3BiLvfPQr8iCPPXlbfgjoe/n+NhCwXazNs=",
"space"=>{"name"=>"The Kosmonaut",
"address"=>"8 Sichovykh Striltsiv 24",
"email"=>"info#somedomain.com"},
"commit"=>"Update Space",
"id"=>"the-kosmonaut"} <--- culprit
I'm guessing the last line is the culprit as it's a readonly attribute, it's not in my form but rather in the PATH
http://localhost:5000/manage/spaces/the-kosmonaut/edit
How can I fix this from trying to update the ID?
Form from in ActiveAdmin looks like this:
form do |f|
f.inputs "Details" do
f.input :name
f.input :address
f.input :email
f.input :phone
f.input :website
end
f.inputs "Content" do
f.input :description
f.input :blurb
end
f.buttons
end
UPDATE: This doesn't work either so it's not the friendly_id?
I tried using #watson's suggestion which should have worked but still got the same error ;-(
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"Rc5PmUYZt3BiLvfPQr8iCPPXlbfgjoe/n+NhCwXazNs=",
"space"=>{"name"=>"The Kosmonaut 23"},
"commit"=>"Update Space",
"id"=>"6933"}
http://localhost:5000/manage/spaces/6933/edit
When I check the record in the console with record.readonly? it returns false
UPDATE UPDATE: removing the scope_to fixes the problem.
scope_to :current_user, :unless => proc{ current_user.admin? }
Only problem is I need the scope_to to prevent users from seeing records they do not own. My guess is (as I'm assuming scope_to normally works with has_many) that my HABTM association causes some weirdness? Ie Users <-- HABTM --> Spaces?
If you only want friendly ID's in the front end and don't care about them inside Active Admin, you can revert the effects of the friendly_id gem for your Active Admin controllers.
I don't know exactly how friendly_id overrides the to_param method, but if it's doing it the normal way, re-overriding it inside all of your Active Admin controllers should fix it, e.g.:
ActiveAdmin.register Foobar do
before_filter do
Foobar.class_eval do
def to_param
id.to_s
end
end
end
end
Even better you could create a before filter in the base Active Admin controller ActiveAdmin::ResourceController so that it is automatically inherited into all your Active Admin controllers.
First add the filter to the config/initializers/active_admin.rb setup:
ActiveAdmin.setup do |config|
# ...
config.before_filter :revert_friendly_id
end
The open up ActiveAdmin::ResourceController and add a revert_friendly_id method, E.g. by adding the following to the end of config/initializers/active_admin.rb:
ActiveAdmin::ResourceController.class_eval do
protected
def revert_friendly_id
model_name = self.class.name.match(/::(.*)Controller$/)[1].singularize
# Will throw a NameError if the class does not exist
Module.const_get model_name
eval(model_name).class_eval do
def to_param
id.to_s
end
end
rescue NameError
end
end
Update: I just updated the last code example to handle controllers with no related model (e.g. the Active Admin Dashboard controller)
Update 2: I just tried using the above hack together with the friendly_id gem and it seems to work just fine :)
Update 3: Cleaned up the code to use the standard way of adding Active Admin before filters to the base controller
You can customize the resource retrieval according to http://activeadmin.info/docs/2-resource-customization.html#customizing_resource_retrieval. Note that you want to use the find_resource method instead of resource, or you won't be able to create new records.
(Check https://github.com/gregbell/active_admin/blob/master/lib/active_admin/resource_controller/data_access.rb for more details)
In your ActiveAdmin resource class write:
controller do
def find_resource
scoped_collection.where(slug: params[:id]).first!
end
end
You can also overwrite the behavior for all resources by modyfing the ResourceController in the active_admin.rb initializer.
ActiveAdmin::ResourceController.class_eval do
def find_resource
if scoped_collection.is_a? FriendlyId
scoped_collection.where(slug: params[:id]).first!
else
scoped_collection.where(id: params[:id]).first!
end
end
end
Hope that helps!
Side note: When creating new records through the admin interface make sure you don't include the slug field in the form, or FriendlyId will not generate the slugs. (I believe that's for version 5+ only)
This method works for me. add this code in app/admin/model_name.rb
ActiveAdmin.register model_name do
controller do
defaults finder: :find_by_slug
end
end
To add to Denny's solution, a more "friendly" solution would be to use friendly_id's finders.
controller do
def find_resource
scoped_collection.friendly.find_by_friendly_id(params[:id])
end
end
Source
Here is my solution based on #Thomas solution
ActiveAdmin.setup do |config|
# ...
config.before_filter :revert_friendly_id, :if => -> { !devise_controller? && resource_controller? }
end
# override #to_param method defined in model in order to make AA generate
# routes like /admin/page/:id/edit
ActiveAdmin::BaseController.class_eval do
protected
def resource_controller?
self.class.superclass.name == "ActiveAdmin::ResourceController"
end
def revert_friendly_id
model_name = self.class.name.match(/::(.*)Controller$/)[1].singularize
# Will throw a NameError if the class does not exist
Module.const_get model_name
eval(model_name).class_eval do
def to_param
id.to_s
end
end
rescue NameError
end
end

Can't dup NilClass on association methods

I'm using rails 2.3.5 and ruby 1.8.7. I'm building a simple TODO manager. Where I have tasks that belong to a user and a user has many tasks.
I'm using acts_as_taggable_on_steroids plugin for tagging tasks and restful_authentication plugin for registration and user management.
I'm getting a weird error that reads "Can't dup NilClass" on the view of index action. This is what the controller code is -
#tasks = current_user.tasks
The error occurs when I'm iterating over #tasks on the view. That is when I do #tasks.each do |task|
Now when I replace the controller code with this
#tasks = Task.find(:all, :conditions => {:user_id => current_user.id})
Which is actually fetching the same records. This happen only in development mode. I am guessing this has something to do with caching or loading.
What could be wrong? I'm facing this issue for the first time.
EDIT
Okay, this is definitely a caching issue. If I make
config.cache_classes = true in production.rb, the same error occurs in production mode as well. But how do I fix that now? Because I don't want to be reloading the server for every change I make in models/controllers.
EDIT
Here is how my User model looks like
class User < ActiveRecord::Base
has_many :tasks
has_many :projects
# There are some validations and standard methods that resful authentication
# provides that I am not showing here
end
And this is how the Task model looks like.
class Task < ActiveRecord::Base
belongs_to :bin
belongs_to :project
belongs_to :user
acts_as_taggable
def tag_list
super.join(', ')
end
end
Task controller's index method looks like this
def index
#tasks = current_user.tasks
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tasks }
end
end
Hope this helps.
Got it.
From here,
Some of the classes inherited or
included in your engine controllers
may fail to get unloaded and cause
trouble after the first request to
your system.
For me, it was because I had a file in the lib that was monkey patching User model and the
User model class in this file was not getting cached I suppose.
Calling unloadable in that class in the lib folder did the trick. So my lib file looks like this
class User < ActiveRecord::Base
unloadable
# stuff...
end
Thanks anyways.
Maybe there is something wrong with associations in model. Can you paste some code from there?
You can also try doing the same in console. Does it give the same error? Take a look in logs, do both of your examples generates the same sql query?

Resources