update_attributes doesn't take a role - ruby-on-rails

I have a model with two attr_accessible lines
attr_accessible ...., :as => :user
attr_accessible ...., :as => :admin
Then in my controller I do this
if #user.update_attributes(params[:user],:as => :user)
And I get wrong number of parameters 2 for 1, however: #user.assign_atrributes(params[:user],:as => :user) works.
I am using mongoid. Any ideas?

I think you found something that has not been implemented yet in your version of Mongoid -- but please check a later Mongoid version, where it seems to be implemented since 2.2.1!
The doc says that :as is a valid option in Mongoid::Document
http://mongoid.org/docs/documents/access.html (see bottom of page)
But it says: You can scope the mass assignment by role by providing the role as an option to the constructor or create methods.
It doesn't speak of update_attributes in particular - update_attributes is not a constructor
Checking in the source code reveals that it's not implemented in update_attributes() in Mongoid versions < 2.2.1 , but later versions have it implemented!
If you're using a later version of Mongoid, and still experience problems,
I would recommend to post this on the Google Mongoid Group as a bug, mentioning your Mongoid version number.
See also:
http://groups.google.com/group/mongoid/
EDIT:
Looks like it's a missing feature in Mongoid 2.1.7
in the mongoid source code, update_attributes() calles write_attributes(), which does not know about the option :as
but if you look at the source code of Mongoid 2.3.1 , you'll see that it's implemented there since 2.2.1!
write_attributes() calls assign_attributes , which honors the option :as
# Allows you to set all the attributes for a particular mass-assignment security role
# by passing in a hash of attributes with keys matching the attribute names
# (which again matches the column names) and the role name using the :as option.
# To bypass mass-assignment security you can use the :without_protection => true option.
#
# #example Assign the attributes.
# person.assign_attributes(:title => "Mr.")
#
# #example Assign the attributes (with a role).
# person.assign_attributes({ :title => "Mr." }, :as => :admin)
#
# #param [ Hash ] attrs The new attributes to set.
# #param [ Hash ] options Supported options: :without_protection, :as
#
# #since 2.2.1
def assign_attributes(attrs = nil, options = {})
_assigning do
process(attrs, options[:as] || :default, !options[:without_protection]) do |document|
document.identify if new? && id.blank?
end
end
end
You can find the source code this way:
$ find ~/.rvm/gems/ruby-1.9.2-p0/gems/mongoid-2.1.7/lib/ -type f -exec grep -l 'def write_attributes' {} \;
~/.rvm/gems/ruby-1.9.2-p0/gems/mongoid-2.1.7/lib/mongoid/attributes.rb
$ emacs ~/.rvm/gems/ruby-1.9.2-p0/gems/mongoid-2.1.7/lib/mongoid/attributes.rb

Related

How can I put a conditional validation on an ActiveModel attribute that has several validations already?

I'm using Authlogic which by default puts a few validations(length, uniqueness, format, etc) on fields like login, email, and password.
I'd like to be able to skip all validations attached to one of the attributes if, say, another attribute is present.
Is this possible? Something like:
validate :email, :unless => :has_openid?
which will then skip the format, length, and uniqueness validations just on the email attribute.
I'm working with a Rails 3.1.x app and authlogic 3.1.0
Update: I was trying to follow this article, but I couldn't get it to work properly:
http://erikonrails.snowedin.net/?p=242
The way I've done this with authlogic is by passing a block to acts_as_authentic:
acts_as_authentic do |config|
with_options :unless => :has_openid? do
config.merge_validates_format_of_email_field_options
config.merge_validates_length_of_email_field_options
config.merge_validates_uniqueness_of_email_field_options
end
end
I've written a workaround and abstracted it out into a gem:
https://github.com/synth/conditional_attribute_validator
Gemfile:
gem 'conditional_attribute_validator', :git => "git://github.com/synth/conditional_attribute_validator.git"
Example:
class User
include ConditionalAttributeValidator
validate_attributes_with_condition :login, :password, :password_confirmation, :unless => :has_another_form_of_authentication?
end
Source:
def validate_attributes_with_condition(*args)
opts = args.extract_options!
raise "Must have an :if or :unless option" unless opts.has_key?(:if) or opts.has_key?(:unless)
merge_methods = self.methods.grep(/merge_.*_options/)
args.each do |field|
merge_methods.grep(/#{Regexp.quote(field)}/).each do |m|
self.send(m, opts)
end
end
end
Rails automagically creates the merge_attr_options methods based upon whatever validations have been specified in order to merge options into an existing validation. So I lookup these methods and iterate over them and check if that method applies to a particular field. If so, I call the merge_attr_options method and pass in the options.
I'm not too concerned about performance since this is just performed on initialization.

ActiveAdmin, polymorphic associations, and custom filters

Rails 3.1, ActiveAdmin 0.3.4.
My question is somewhat similar to this one but different enough in terms of data modeling that I think it warrants its own response. Models:
class CheckoutRequest < ActiveRecord::Base
has_one :request_common_data, :as => :requestable, :dependent => :destroy
end
class RequestCommonData < ActiveRecord::Base
belongs_to :requestable, :polymorphic => true
end
The RequestCommonData model has a completed field (boolean) that I'd like to be able to filter in ActiveAdmin's CheckoutRequest index page. I've tried a few different approaches to no avail, including the following:
filter :completed, :collection => proc { CheckoutRequest.all.map { |cr| cr.request_common_data.completed }.uniq }
which results in no filter being displayed. Adding :as => :select to the line, as follows:
filter :completed, :as => :select, :collection => proc { CheckoutRequest.all.map { |cr| cr.request_common_data.completed }.uniq }
results in the following MetaSearch error message:
undefined method `completed_eq' for #<MetaSearch::Searches::CheckoutRequest:0x007fa4d8faa558>
That same proc returns [true, false] in the console.
Any suggestions would be quite welcome. Thanks!
From the meta_search gem page you can see that for boolean values the 'Wheres' are:
is_true - Is true. Useful for a checkbox like “only show admin users”.
is_false - The complement of is_true.
so what you need is to change the generate input name from 'completed_eq' to be 'completed_is_true' or 'completed_is_false'.
The only way I have found this possible to do is with Javascript, since by looking at the Active Admin code, the 'Wheres' are hardcoded for each data type.
I would usually have a line like this in my activeadmin.js file (using jQuery)
$('#q_completed_eq').attr('name', 'q[completed_is_true]');
or
$('#q_completed_eq').attr('name', 'q[completed_is_false]');
Terrible and ugly hack but have found no other solution myself.
Be careful to enable this only in the pages you want.
--- NEW FOR VERSION 0.4.2 and newer ---
Now Active Admin uses separate modules for each :as => ... option in the filters.
So for example you can place the code below inside an initializer file
module ActiveAdmin
module Inputs
class FilterCustomBooleanInput < ::Formtastic::Inputs::SelectInput
include FilterBase
def input_name
"#{#method}_is_true"
end
def input_options
super.merge(:include_blank => I18n.t('active_admin.any'))
end
def method
super.to_s.sub(/_id$/,'').to_sym
end
def extra_input_html_options
{}
end
end
end
end
and the use
:as => :custom_boolean
where you specify your filter.

Rails 3.1 attr_accessible verification receives an array of roles

I would like to use rails new dynamic attr_accessible feature. However each of my user has many roles (i am using declarative authorization). So i have the following in my model:
class Student < ActiveRecord::Base
attr_accessible :first_name, :as=> :admin
end
and i pass this in my controller:
#student.update_attributes(params[:student], :as => user_roles)
user_roles is an array of symbols:
user_roles = [:admin, :employee]
I would like my model to check if one of the symbols in the array matches with the declared attr_accessible. Therefore I avoid any duplication.
For example, given that user_roles =[:admin, :employee]. This works:
#student.update_attributes(params[:student], :as => user_roles.first)
but it is useless if I can only verify one role or symbol because all my users have many roles.
Any help would be greatly appreciated
***************UPDATE************************
You can download an example app here:
https://github.com/jalagrange/roles_test_app
There are 2 examples in this app: Students in which y cannot update any attributes, despite the fact that 'user_roles = [:admin, :student]'; And People in which I can change only the first name because i am using "user_roles.first" in the controller update action. Hope this helps. Im sure somebody else must have this issue.
You can monkey-patch ActiveModel's mass assignment module as follows:
# in config/initializers/mass_assignment_security.rb
module ActiveModel::MassAssignmentSecurity::ClassMethods
def accessible_attributes(roles = :default)
whitelist = ActiveModel::MassAssignmentSecurity::WhiteList.new
Array.wrap(roles).inject(whitelist) do |allowed_attrs, role|
allowed_attrs + accessible_attributes_configs[role].to_a
end
end
end
That way, you can pass an array as the :as option to update_attributes
Note that this probably breaks if accessible_attrs_configs contains a BlackList (from using attr_protected)

how to index associated models using thinkingtank and indextank

We are using thinkingtank gem and having trouble indexing model associations, even simple ones. For example, a profile belongs to an institution, which has a name – we would like to do something like:
class Profile < ActiveRecord::Base
#model associations
define_index do
indexes institution(:name), :as => :institution_name
end
end
but that doesn't work. This must be very simple – what am I doing wrong?
a possible solution to this issue would be adding a method returning the element to index. For the profile.institution.name case:
# profile.rb
# ...
belongs_to :institution
# ...
define_index do
indexes institution_name
end
def institution_name
self.institution.name
end
# ...
Also the ", :as => ..." syntax is not supported on thinkingtank.
I would also recommend giving a try to Tanker: https://github.com/kidpollo/tanker
Regards.
Adrian

MongoMapper has_many association

I have problem with mongomapper associations. I have one class names User and other named Model. User has many models but...
user = User.first
=> <User ...
user.models
=> []
Model.find_by_user_id(user.id.to_s)
=> <Model ...
Model.find_by_user_id(user.id.to_s).user == user
=> true
Class code (simplified):
class User
include MongoMapper::Document
# some keys definition
many :models
end
class Model
include MongoMapper::Document
# some keys definitions
belongs_to :user
end
What I am doing wrong?
It appears that MM no longer uses String format for the FK column, so
Model.find_by_user_id(user.id.to_s)
should be
Model.find_by_user_id(user.id)
Furthermore, the datatype of the Model.user_id column should be set to
key :user_id, Mongo::ObjectID
When I ran into this problem, I had to delete and recreate my collection to get it to work- in other words I used to have user_id as a String, but it would only "take" when I switched it when I rebuilt my database. Luckily I am working with test data so that was easy enough.
What kind of errors or exceptions are you getting? The code you posted looks fine.
ah, this is poorly documented in the mm docs. You need to do this here:
class User
include MongoMapper::Document
# some keys definition
many :models, :in => :model_ids
end
class Model
include MongoMapper::Document
# some keys definitions
# no belongs_to necessary here
end
You can then add models to your user via:
# use an existing object
u = User.create ...
m = Model.create ...
# and add the model to the user
u.models << m
# don't forget to save
u.save
# you can then check if it worked like so:
# u.model_ids => [ BSON::ID 'your user id']
Hope that helped.

Resources