Rails 3.1 route constraint fails - ruby-on-rails-3

This question is comparable to this one, but I ask it again because the answer provided does not solve the issue, and as the person asking that question concludes: it might be a bug in Rails (but no follow up on that is given).
I have these routes:
resources :books, controller: :projects, type: "Book" do
resources "", as: :book_chapters, controller: :contributions, type: "BookChapter", except: :index, constraints: IdConstraint
end
This is the IdConstraint:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
!INVALID_IDS.include? request.params[:id]
end
end
I use friedly_id, so the :id parameter is a string based on the title of a book or book chapter.
Now a request like /books/friendly-id-of-book/friendly-id-of-chapter routes to the show action on the book_chapters controller.
But I would also expect /books/friendly-id-of-book/edit to route to the edit action on the books controller, because the constraint on the book_chapters route exclude edit as id. This does not work, and it seems that the problem is in IdConstraint. If I replace the one line in the matches? method with false:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
false
end
end
the .../edit route properly routes to the edit action on the books controller.
But when i only add a line with false after the original line:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
!INVALID_IDS.include? request.params[:id]
false
end
end
the route fails, i.e. it routes to the book_chapters controller with id "edit", while I would really expect it to still return false, and thus route to the edit action of the books controller.
I can't figure out what's going wrong here. Any ideas?

It looks to me like what you're running up against is a scope issue. INVALID_IDS is defined outside self.matches?(), so it's not available inside self.matches?().
You could try:
class IdConstraint
def self.matches?(request)
INVALID_IDS = %w[edit series new]
!INVALID_IDS.include? request.params[:id]
end
end
Or, if you really need INVALID_IDS to be available elsewhere in IdConstraint, you can do:
# Note that you'll need to use IdConstraint.new as your constraint for
# this one, not IdConstraint
class IdConstraint
def initialize
#INVALID_IDS = %w[edit series new]
end
def matches?(request)
!#INVALID_IDS.include? request.params[:id]
end
end
There's a good example of the second method on the Rails Guide.
UPDATE
Yeah, you're right about the bug with request.params. For now, this seems to work relatively well:
class IdConstraint
INVALID_IDS = %w[edit series new]
def self.matches?(request)
end_of_path = request.path.split('/').last
!INVALID_IDS.include? end_of_path
end
end

This is caused by a bug in Rails 3.1 master (at 0e19c7c414bb). See the bug report on GitHub. Until it is fixed you can temporarily circumvent it by using request.path_parameters[:id] instead of request.params[:id].

Related

Inherited_resources build resource as role

As an example:
def create
resource = build_resource
resource.assign_attributes(params[resource_instance_name], as: :admin)
create! do |format|
format.js {...}
end
end
The problem with above is that attributes are not being assigned with as: :admin, they are being assigned without any check and so this method is not having any effect. Is it the create! method? Attributes are being assigned to this resource elsewhere and I can't find out where it is. Appreciate any insight.
Found my answer here: https://github.com/josevalim/inherited_resources/pull/153. Had to override as_role and role_given? --
def as_role
{ as: current_user.highest_role }
end
def role_given?
true
end
This will then always apply roles to attributes for either the controller it is defined in, or all resources if you inherit from a master resources controller like I do.

using build method on association in rails 3.2 creating object in memory

I have 2 models like the following
Class Post
has_many :comments, :dependent => :destroy
end
Class Comment
validates_presence_of :post
validates_presence_of :comment
belongs_to :post
end
In Comments controller,
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
// some code
end
end
When the comment is invalid as per the validation, the comment is not saved. But when the #post object is accessed in the view, it contains a comment object with nil id. This did not happen in Rails 2.3.11. We are upgraded to Rails 3.1 and then now to Rails 3.2. This comment object with nil id disappears when I do #post.reload. We are using REE.
I tried to interchange build and new methods. It had the same result as build. Similar behavior is found across our application. Is it the expected behavior or am I doing something wrong?
This seems like expected behaviour to me.
via http://guides.rubyonrails.org/association_basics.html#belongs_to-association-reference
4.1.1.3 build_association(attributes = {})
The build_association method returns a new object of the associated
type. This object will be instantiated from the passed attributes, and
the link through this object’s foreign key will be set, but the
associated object will not yet be saved.
When you call #post.comments.build(...), Rails:
Creates a new Comment object
sets comment.post_id to #post.id.
Inserts it into the comments array (in memory).
When the validation fails, it doesn't delete the comment and the comment persists in the in-memory comments array. When #post gets to your view, #post.comments still includes that badly validated comment.
As for how to deal with it, I'm not sure. Maybe you could do something like (in your controller)... (Feels pretty ugly though.)
def create
comment = #post.comments.build(params[:comment])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
I had a similar problem while using rails 3.2
Firstly, you need to create two separate methods in your controller. They will be as follows:
The 'new' method that is used to build your comments using 'build_association'
def new
#post = Post.new
comment = #post.build_comments
end
The 'create' method to actually create your comments using 'create_association'
def create
#post = Post.new(params[:post])
comment = #post.create_comments(params[:post][:comment_attributes])
if comment.save
// some code
else
#bad_comment = #post.comments.pop
end
end
Note: I suggest passing 'comment' attribute as a nested attribute of 'post' through your form using 'fields_for'.
Please refer:
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Rails, creating a callback

I want to use an ActiveModel callback to be called after an object has been voted on, the issue is that the gem I'm using (voteable_mongo) to make the model votable doesnt provide like a vote model or callback in my app, so how can I create a callback for it?
set_callback(:vote, :before) do |object|
object.do_something
end
Obviously that vote action I made up, but the gem I'm using has this method, how would you properly extend this method to trigger a callback?
Taking the plugin example as source here's what you could do:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
def vote(options, value = nil)
_run_vote_callbacks do
super( options, value )
end
end
end
I did not run this code so I am not sure if this is going to work correctly or not, but in the worst case you could alias the vote method using alias_method_chain or just copy and paste the source to inside the _run_vote_callbacks block (really, really ugly, but it's a solution anyway).
EDIT
This could also be done using alias_method_chain, if the code above does not work:
class Post
include Mongoid::Document
include Mongo::Voteable
extend ActiveModel::Callbacks
define_model_callbacks :vote
# set points for each vote
voteable self, :up => +1, :down => -1
alias_method_chain :vote, :callback
def vote_with_callback(options, value = nil)
_run_vote_callbacks do
vote_without_callbacks( options, value )
end
end
end

Cells with Declarative_Authorization

using the gems cells and declarative_authorization (along with Devise) and I'm trying to figure out how to include the permitted_to? into the cell templates. So far I've added this to my cells Cell (the Devise one works for it's helpers):
class SidebarCell < Cell::Rails
include Devise::Controllers::Helpers
helper_method :current_user
include Authorization::AuthorizationHelper
helper_method :permitted_to?
def display(args)
#object = args[:object]
#notice = args[:notice]
#alert = args[:alert]
render
end
end
But it's bombing at the fact that declarative_auth helper module uses the following code:
def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
controller.permitted_to?(privilege, object_or_sym, options, &block)
end
and obviously this gives
undefined local variable or method `controller' for ...
UPDATE:
After some more thinking, I'm not sure this would ever work with Cells. Declarative_auth needs the controller to base it's rules on, but Cells has nothing to do with that controller. It would seem to me that the two are incompatible, unless I pass a reference to the controller into Cells? Starting to think that Cells isn't the way to go.
This will work if you add
helper_method :controller
that just delegates:
def controller
parent_controller
end
Sorry for that inconvenience, but the entire helper architecture in Rails sucks: http://nicksda.apotomo.de/2011/10/rails-misapprehensions-helpers-are-shit/

What am I doing wrong with this rspec helper test?

All I'm trying to do is spec how a one line helper method for a view should behave, but I'm not sure what kind of mock object, (if any) I should be creating if I'm working in Rails.
Here's the code for events_helper.rb:
module EventsHelper
def filter_check_button_path
params[:filter].blank? ? '/images/buttons/bt_search_for_events.gif' : '/images/buttons/bt_refine_this_search.gif'
end
end
And here's my spec code, in events_helper_spec.rb:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe EventsHelper do
#Delete this example and add some real ones or delete this file
it "should be included in the object returned by #helper" do
included_modules = (class << helper; self; end).send :included_modules
included_modules.should include(EventsHelper)
end
it "should return the 'refine image search' button if a search has been run" do
# mock up params hash
params = {}
params[:filter] = true
# create an instance of the class that should include EventsHelper by default, as the first test has verified (I think)
#event = Event.new
# call method to check output
#event.filter_check_button_path.should be('/images/buttons/bt_search_for_events.gif')
end
end
When I've looked through the docs here - http://rspec.info/rails/writing/views.html, I'm mystified as to where the 'template' object comes from.
I've also tried looking here, which I thought would point me in the right direction, but alas, no dice. http://jakescruggs.blogspot.com/2007/03/mockingstubbing-partials-and-helper.html
What am I doing wrong here?
Thanks,
Chris
You are not doing anything in that spec, just setting a stub, so it will pass, but hasn't tested anything.
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe EventsHelper do
it "should return the 'refine image search' button if a search has been run" do
# mock up params hash
params = {:filter => true}
helper.stub!(:params).and_return(params)
helper.filter_check_button_path.should eql('/images/buttons/bt_search_for_events.gif')
end
end
I'm running my test without spec_helper (Ruby 1.9)
require_relative '../../app/helpers/users_helper'
describe 'UsersHelper' do
include UsersHelper
...
end
Ah,
I asked this question on the rspec mailing list, and one kind soul (thanks Scott!) explained to me that there's a handy helper object for this, that you should use instead, like so:
Rails has its own helper function
params = {:filter => true}
helper.stub!(:params).and_return(params)
I've now updated the code like so:
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
describe EventsHelper do
#Delete this example and add some real ones or delete this file
it "should be included in the object returned by #helper" do
included_modules = (class << helper; self; end).send :included_modules
included_modules.should include(EventsHelper)
end
it "should return the 'refine image search' button if a search has been run" do
# mock up params hash
params = {}
params[:filter] = true
helper.stub!(:filter_check_button_path).and_return('/images/buttons/bt_search_for_events.gif')
end
end
And it's working. Huzzah!