Rails, get Class from Class name? - ruby-on-rails

I have an issue here where I'm trying to call a class method on an object that is not known... err, I'm not sure how to phrase this I'm getting the :resource from the URL, but I want to run find on it for a differnt param as well.
How can I do something like:
params[:resource].classify.find(params[:id])
I mean this won't work because the params[:resource].classify would be a string. But how can I run a method on it as if it was a Class and not a string?
The following used to work fine but the gem friendly_id has made all my calls to a record to return it's friendly_id and not its actual primary key... which totally sucks.
It was doing something like this, which worked just fine:
#vote = Vote.new({
:vote => params[:direction] == 'up' ? true : false,
:voteable_type => params[:resource].classify,
:voteable_id => params[:id])
})
But since adding friendly_id my paths now look something like:
/things/my-thing-name/vote/up
instead of the old way:
/things/328/vote/up
So now the params[:id] is no longer the foreign key.
Thoughts?

I'm a bit confused by your question, it seems like 2.
For part 1, you can constantize. params[:resource].classify.constantize should return the classname that you can then invoke a method on. Just to be safe, you might want to tableize before constantizeing, just to make sure things like "-" are going to be "_". I only mention this because of how you have your friendly_id set up.
As for part 2, I don't know the friendly_id gem, but based off of the description of how it works in the guide, your find should still work just fine unless I'm missing something.

For the first thing "params[:resource].classify" you need to do
params[:resource].constantize
For the second it looks like you should do something like:
#thing = Thing.find_by_friendly_id(params[:friendly_id])

Related

Rails gem rails3-jquery-autocomplete how to scope by user

I'm using the Rails gem rails3-jquery-autocomplete to add categories to posts.
I would like to restrict the search to include only categories that belong to the current user or post's author in the results.
The documentation says that I can specify a scope:
:scopes
Added option to use scopes. Pass scopes in an array. e.g :scopes =>
[:scope1, :scope2]
But I'm not sure how I would pass the user id here?
It seems like a comon scenario, am I missing something obvious?
I found an answer that suggests modifying the get_item method, but that seems to break the auto-complete
Scoping the results for rails3 jquery autocomplete plugin
In posts_controller:
def get_autocomplete_items(parameters)
items = super(parameters)
items = items.where(:user_id => current_user.id)
end
I'm first calling the original get_autocomplete_items method, and then filtering out the results by current_user.id.
This question helped:
Rails 3: alias_method_chain still used?
I had a similar problem I solved thanks to the answers above.
My autocomplete also worked against a User model, but I needed to restrict the results to the user's institution (Institution has many :users). My controller creates an #institution instance variable that is accessed in the view.
Although the get_autocomplete_items method cannot directly access the instance variable, I found that the data CAN be passed to autocomplete as a parameter (note: I use the simple_forms gem, so the input call looks a little different than the standard rails syntax).
In my view:
<%= f.input :email, :url => autocomplete_user_email_institutions_path(:institution_id=>#institution.id.to_s), :as => :autocomplete %>
In my controller:
autocomplete :user, :email, :extra_data => [:first_name, :last_name]
def get_autocomplete_items(parameters)
super(parameters).where(:institution_id => params[:institution_id])
end
My autocomplete list is now scoped to just the users who work for a particular institution.
deb's answer works for me.
The code can be cleaned up a bit:
def get_autocomplete_items(parameters)
super(parameters).where(:user_id => current_user.id)
end
There is small update to code for those who have having trouble with super method.because of dynamic dispatch it above code need to replaced as below:
def get_autocomplete_items(parameters)
items = super(parameters)
items = items.where(searchable: true)
end
to this:
def get_autocomplete_items(parameters)
items = active_record_get_autocomplete_items(parameters)
items = items.where(searchable: true)
end
Reference: https://github.com/crowdint/rails3-jquery-autocomplete/issues/278
To answer the question posed by #ctilley79, multiple autocompletes is not a problem because, in addition to the possibility of passing more values in the params hash, you also have access to the autocomplete parameters. On my form (as an example), I have both a City and a Zip autocomplete. I need to restrict the City to those in a certain state. So my controller action looks like this:
def get_autocomplete_items(parameters)
if (parameters[:model] == City)
super(parameters).where("state_id" => params[:state_id])
else
super(parameters)
end
end
You also have access to the method in case you need it. Do logger.debug on the parameters to see all that is available.
I know the gem and the question are old but I found myself using this gem and needing this answer recently... None of the old answers will work anymore because in the source code, the method get_autocomplete_items is generated dynamically and has the ORM prepended on the method name. This is what got it working for me. I assume most folks are using ActiveRecord too but check the autocomplete.rb method 'get_prefix' to figure out what you should prepend to the method name to get it working.
Hope this saves someone a bunch of time. Be the change you want to see and all that ;)
def active_record_get_autocomplete_items(parameters)
super(parameters).where(id: current_user.id)
end
I faced a similar problem. Our site is multi-tenant, so everything needs to be scoped to the tenant.
To make this easier, I modified rails3-jquery-autocomplete to accept another option called :base_scope. It takes a string, that gets eval'd instead of using the model. All the other functionality works, so you can append additional scopes and where clauses if you need to.
My fork is here: https://github.com/GiveCorps/rails3-jquery-autocomplete
I am not sure that the tests i wrote prove it will always work. I just checked that it was using the scope instead of the model in the items method.
i would appreciate any thoughts on it. Not sure whether it merits a pull request.

Change From :Id in URL in Rails 3 Routing

Boy this seems like a piece of cake, but I can't find it in the routing bible --
Is there a way to change the default parameter ':id' to something else like ':pid' without using 'match /post/:pid'? I want to avoid using 'match' because it feels particularly brittle.
Edit To confirm, this is only a success if i can do:
pid = params[:pid]
Doing:
pid = params[:id]
works already, but is wrong code, because it's not an id in there.
If I got you right check this out:
http://railscasts.com/episodes/63-model-name-in-url
Basically you should override to_param method in the model:
def to_param
pid
end
And when you will want to fetch an object, do this in the controller:
#object= Object.find_by_pid(params[:id])
Good luck!
You can define a to_param method in your model:
def to_param
pid
end
Then all of your generated links, etc. will use the pid instead of id. And in your controller, params[:id] will actually give you pid, not id.

How do you stub a `current_user` with update_attributes set to false?

This is a pure syntactical question. I'm very new to RSpec.
I basically want to write something on the lines of this erroring line :
controller.stub!(:current_user(:update_attributes => false))
Anyone know how to properly write that?
RSpec's default looks like this :
User.stub(:find) { mock_user(:update_attributes => false) }
This looks like a case for stub_chain:
controller.stub_chain(:current_user,:update_attributes).and_return(false)
Note that this is just going to replace methods in the list in the order they occur, so for this to make sense you'll have a current_user.update_attributes in your controller. If you have something like #user.update_attributes, I don't think it will work.
More info on APIDock
I just blindly played around with a thousand variations and finally got it to pass with this :
controller.stub!(:current_user).and_return(#user)
#user.stub!(:update_attributes => false)
But seriously, does that even make any sense? It's passing :D
With RSpec 3.0 and rspec-mocks the way to do this is to use allow.
before(:each) do
#user = mock_model(User)
allow(controller).to(receive(:current_user).and_return(#user))
allow(#user).to(receive(:update_attributes).and_return(false))
end
This does not address update_attributes specifically, but the following worked for me. Assuming your current_user helper lives in ApplicationController:
user = double(:user)
allow(controller).to(receive(:current_user).and_return(user))
allow(controller.current_user).to receive(:is_admin?).and_return(false)
current_user.is_admin? # => false

How to implement "short" nested vanity urls in rails?

I understand how to create a vanity URL in Rails in order to translate
http://mysite.com/forum/1 into http://mysite.com/some-forum-name
But I'd like to take it a step further and get the following working (if it is possible at all):
Instead of:
http://mysite.com/forum/1/board/99/thread/321
I'd like in the first step to get to something like this: http://mysite.com/1/99/321
and ultimately have it like http://mysite.com/some-forum-name/some-board-name/this-is-the-thread-subject.
Is this possible?
To have this work "nicely" with the Rails URL helpers you have to override to_param in your model:
def to_param
permalink
end
Where permalink is generated by perhaps a before_save
before_save :set_permalink
def set_permalink
self.permalink = title.parameterize
end
The reason you create a permalink is because, eventually, maybe, potentially, you'll have a title that is not URL friendly. That is where parameterize comes in.
Now, as for finding those posts based on what permalink is you can either go the easy route or the hard route.
Easy route
Define to_param slightly differently:
def to_param
id.to_s + permalink
end
Continue using Forum.find(params[:id]) where params[:id] would be something such as 1-my-awesome-forum. Why does this still work? Well, Rails will call to_i on the argument passed to find, and calling to_i on that string will return simply 1.
Hard route
Leave to_param the same. Resort to using find_by_permalink in your controllers, using params[:id] which is passed in form the routes:
Model.find_by_permalink(params[:id])
Now for the fun part
Now you want to take the resource out of the URL. Well, it's a Sisyphean approach. Sure you could stop using the routing helpers Ruby on Rails provides such as map.resources and define them using map.connect but is it really worth that much gain? What "special super powers" does it grant you? None, I'm afraid.
But still if you wanted to do that, here's a great place to start from:
get ':forum_id/:board_id/:topic_id', :to => "topics#show", :as => "forum_board_topic"
Take a look at the Rails Routing from the Outside In guide.
maybe try something like
map.my_thread ':forum_id/:board_od/:thread_id.:format', :controller => 'threads', :action => 'show'
And then in your controller have
#forum = Forum.find(params[:forum_id])
#board = #forum.find(params[:board_id])
#thread = #board.find(params[:thread_id])
Notice that you can have that model_id be anything (the name in this case)
In your view, you can use
<%= link_to my_thread_path(#forum, #board, #thread) %>
I hope this helps

Overriding Rails to_param?

How do I get the to_param method to deliver keyword slugs all the time? I have trouble getting it to work with this route:
map.pike '/auction/:auction_id/item/:id', :controller => 'items', :action => 'show'
Earlier the overridden to_param was working for
'items/1-cashmere-scarf'
but fails with 'auction/123/item/1'
Update:
I'm not sure if the syntax is correct[(edit: it's correct: it works :-)], or even efficient.... but using haml, I found that the following code works to generate the desired link ('auction/:auction_id/item/:id')
- for auction in #auctions.sort{|a, b| a.scheduled_start <=> b.scheduled_start}
-for item in #items
- unless auction.current_auction
... pike_path(auction.auction_id, item)
I'm not sure whether I understand your question. (it's 3:41 AM here)
From what I see, you directly access auction_id method, instead of using pike_path(auction, item) that'd use #to_param.
Also, it might fail for auction/123/item/1 because you haven't changed your controller.
I think it'd be helpful to describe how to get working slugs.
Broadly speaking, if you override #to_param, IDs no longer works. It means, that if you go with slugs, every time polymorpic URL is generated (eg, link_to object, object), it passes to_param's value. It is worth noting that you must change your controller as well.
Personally I think that the best way to generate slugs easily is to use techno-weenie's permalink_fu, adding has_permalink to your model, and then, override to_param. For example
class Auction < ActiveRecord::Base
has_permalink :title, :slug
end
assuming that you have slug, a string field, and want to slugize your title.
You also need to adjust your controller:
class AuctionsController < ApplicationController
def show
#auction = Auction.find_by_slug(params[:id]) || raise(ActiveRecord::RecordNotFound)
respond_to do |format|
format.html # show.html.erb
end
end
Then, you can generate routes, in the views, this way:
link_to #action, #action
By the way, you should NOT sort your actions in the view. The best way is to use named_scope.

Resources