Cant update integer fields in rails - ruby-on-rails-3

Okey i just dont understand what can be wrong here
i have this app where users are approved by and admin and this was working fine until a few days ago
in my view i have a link that calls my user controller
<%= link_to 'Approve', active_user_path(user), :method => :put %>
here is my custum route for that link
match "users/:id/activate" => "users#activate", :as => "active_user"
now in my user controller i have this activate method
def activate
#user = User.find(params[:id])
puts #user.name #the correct name is displayed
puts #user.is_approved.inspect.to_i #:is_approved is 0
if #user.update_attribute(:is_approved, 1)
puts #user.is_approved.inspect # :is_approved is 1
#user.activate_user
puts #user.is_approved.inspect # :is_approved is 1
#user.save!
redirect_to "/users?is_approved=0"
else
render "/" # dosn't matter
end
end
I try to save 3 times here (update, activate_user, save!) but still the value will not be saved, the users is_approved field is still 0, how is that possible ?
here is my model method
def activate_user
self.is_approved = 1
self.save
end
btw i can update strings with this method but not integers (true and false dosnt work either)
in my model i have is_approved as both attr_accessible and attr_accessor
The solution
Well this is awkward but so it happens that in my user model i had attr_accessor :approved this resulted in that the model never went to the database to update the :approved column BUT instead it updated the local variable :approved so next time when i looked at the column then of course the :approved value had not changed
tldr?
if you have attr_accessor in your model with the same name as the column your trying to update => remove it

Never use attr_accessor on an attribute which is backed by a database column - the accessor generated by attr_accessor will mask the value stored in the database

update_attribute actually does more than just updating a single column:
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
Are there any callbacks in your User model?
Make sure the column is not being updated somewhere in a callback.

Related

Override a form field value in rails form

Note: I was overthinking things when I originally asked this question. The accepted answer is correct for the examples I gave - i.e. you can just pass :value to text_field, however I'd actually been having problems with date_select, which doesn't have a facility to override the value set.
As a result this has now been updated in Rails, so you can set :selected => a_date, and it will work as expected. This will be in Rails 4.
I have a model that can inherit its value from a parent model. It works something like this:
class User < ActiveRecord::Base
attr_accessible :field_name
belongs_to :company
def field_name
if self['field_name'].nil?
company['field_name']
else
self['field_name']
end
end
end
class Company < ActiveRecord::Base
attr_accessible :field_name
end
I then have a form to edit the User, but of course, if the User value is nil, then it populates the form with the value from Company, which is not what I want.
I would like to be able to override the value of the form field, so that if the User value is nil, then the value is empty.
Attempt 1
Ideally I'd be able to do:
<%= form_for #user do |f| %>
<%= f.text_field :field_name, #user['field_name'] %>
<% end %>
But that doesn't work, there doesn't seem to be a mechanism for providing an override value.
Attempt 2
So I thought about creating a second getter/setter:
def field_name_uninherited
self['field_name']
end
def field_name_uninherited=(value)
self['field_name']=value
end
Now I can use <%= f.text_field :field_name_uninherited %> and it works as expected - great! Except: when field_name is a date, or other type using multiparameter attributes, it results in this error:
1 error(s) on assignment of multiparameter attributes
I believe this is because it doesn't know that this is a date field, as it infers this from the database, and this field (with _uninherited suffix) is not in the database.
So I need some way to mark my additional method as the same type as the original database field.
A further note, the above examples (using field_name) are a simplified version. I'm actually using https://github.com/colinbm/inherits_values_from to handle the inheritance, but I don't think this is important to the question.
Obviously if there's a better way to accomplish the same goal, then I'm all ears.
So when it comes to displaying the value you for a user you want it to behave a bit differently?
What I'd do is use the :value option with your form field. That way you get to set the value like normal but choose what you want displayed in the form field.
<%= f.text_field :company, :value => user.field_name_uninherited %>
For what I understand, you want the user to put the field data and only if it's nil, populate that value with the parent (company) model. It seems to me before_save works perfectly, because it is called (as it name proposes) just before the save method is called on an ActiveRecord object.
Thus you can write this kind of callback:
class User < ActiveRecord::Base
attr_accessible :field_name
before_save :override_field
private
def override_field
if self.field_name.nil?
self.field_name = company.field_name
end
end
This way, you'll be only overriding the value if it's nil at the moment of saving, leaving that form field empty at the moment of creating a new element. Hope this works!

Can I set a nested attribute in the controller vs. the view

I have a simple nested form and I am setting a nested attribute with a hidden field:
<%= role_form.hidden_field :company_id, :value => session[:company_id] %>
The idea here is I am associating this nested model (a role based permissions system) to another model Company via the company_id set by the current session variable. My issue is that the user could send a request and create / update the role with an arbitrary company_id and gain access to another company's account.
Can I force the nested model attributes to be this session value or perhaps a validation?
I was thinking for create:
#user = User.new(params[:user])
#user.roles.first.company_id = session[:company_id]
and for update I could do sort of the same thing.
As for the validation I tried:
accepts_nested_attributes_for :roles, :limit => 1, :allow_destroy => true , :reject_if => proc { |attributes| attributes['company_id'] != session[:company_id] }
but it looks like you can't access the session info in the model.
Any one have an idea if I can do this either of these ways?
Rather than storing the company_id in the session, you should instead add a randomly generated token column to the company, and get the id by doing Company.find_by_token(session[:token]). If you look at how the current_user method in this Railscast on authentication, it's the same idea.
Edit:
Sorry, I misunderstood your question. You should not have a hidden company_id field at all in your view. You should be setting it manually in your create method:
#user = User.new(params[:user])
#user.company_id = session[:company_id]
And you can protect the company_id from ever being set from the user changing an input name by having company_id protected against mass assignment in the model:
attr_protected :company_id
See the rails guide on mass assignment protection for more information. Note: a more common solution is something along these lines:
class ApplicationController < ActionController::Base
protect_from_forgery
def current_company
#current_company ||= Company.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
end
class User < ApplicationController
def create
#user = current_company.users.build(params[:user])
end
end
UPDATE 2:
So you're creating a user and a role, and want to do separate validation on them, this should do what you want.
role_params = params[:user].delete :role # Change to the appropriate symbol for your form
#user = User.new(params[:user])
role = #user.roles.build(role_params)
role.company_id = session[:company_id]
if(#user.save and role.user_id = #user.id and role.save) # Might want to just check for valid instead of trying to save
...

after_validation find existing record's id in Rails

I'm fairly new to Ruby on Rails and within my app the user is able to create a 'build' record that will only be saved if the entire record is unique. If a user tries to create an existing 'build' / record and the validation fails, I need to be able to redirect that user to the existing record.
As I have stated, I am a novice and made a valiant attempt at using the parameters passed to my create action as so:
def create
#build = Build.new(params[:build])
if #build.save
redirect_to :action => 'view', :id => #build.id
else
#bexist = Build.find(params[:build])
redirect_to :action => 'view', :id => #bexist.id
end
end
Clearly this isn't correct... I also tried to look into callbacks with after_validation, but wasn't sure how to access or even store the existing record's id. Anyone have any suggestions?
You need the attribute/value hash to be passed as the :conditions option, and you need to specify :first, :last, or :all as the first argument.
#bexist = Build.find(:first, :conditions => params[:build])
Alternatively, you can use the #first method instead of using #find with a :first argument.
#bexist = Build.first(:conditions => params[:build])
In Rails 3, you have yet another option...
#bexist = Build.where(params[:build]).first

Accept terms of use rails

What is the best way to add a check for accepting terms of use in a rails app?
I can't seem to get validates_acceptance_of working quite right. I added a bool to my user model (was that necessary?). And then have a checkbox that returns either true/false.
I feel like I'm just making a silly little mistake. Any ideas?
In your model,
validates_acceptance_of :terms
If you're using attr_accessible in your model then make sure you also add,
attr_accessible :terms
In your view,
<%= form_for #user do |f| %>
...
<%= f.check_box :terms %>
...
<% end %>
There is no need for an extra column in the users table unless you plan on denying access to users who have not accepted the terms of service, which won't exist since they can't complete registration in the first place.
This is a working Rails 4 solution:
Terms of service doesn't need to be a column in the database
Form
= f.check_box :terms_of_service
models/user.rb
validates :terms_of_service, acceptance: true
And most important, devise will sanitize your parameters and terms of service will be removed from the submitted params. So:
registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) do |u|
u.permit(:full_name,
:email, :password, :password_confirmation, :terms_of_service)
end
end
end
This is a working solution for Rails-6.1 (I18n) + Devise-4.8.0 + SimpleForm. No need to add a column in the "users" table in DB.
View
<% label_str = t('read_html', mylink: link_to(t('terms'), '/a/b/c')) %>
<%= f.input :terms_of_service, label: label_str, as: :boolean, checked: false %>
Or, if you use Indo-European languages only, you can make it a little more simple, like:
label_str = (t('agree_html')+link_to(t('terms'), '/a/b/c')+'.').html_safe
/app/models/user.rb
attr_accessor :terms_of_service
validates_acceptance_of :terms_of_service, on: :create
validates_presence_of :terms_of_service, on: :create
/app/controllers/application_controller.rb
Devise::ParameterSanitizer::DEFAULT_PERMITTED_ATTRIBUTES[:sign_up] << :terms_of_service
# see /vendor/bundle/ruby/*/gems/devise-*/lib/devise/parameter_sanitizer.rb
Explanation
In the User model, on: create guarantees it is read only in creation. If you need to reevaluate the condition in updating, too, specify it accordingly, like on: %i(create update).
In the User model, I add validates_presence_of to play safe. The reason is, validates_acceptance_of will not be executed when the parameter terms_of_service is nil, in which case validates_presence_of will catch it and set an error. Admittedly, if the data are always submitted via the web-interface you have built AND your implementation is working perfectly, the value should be always either true or false and never be nil. So, validates_presence_of should not be necessary in this sense. It does no harm, though (except you'd need to be a little careful in manual user creation, bypassing the web-interface, such as from the Console).
The last one is neccesary for use with Devise for the same reason as in the answer by #vladCovaliov; that is, to prevent Devise from sanitizing your custom parameter, which is not a column in the database table. The one-liner in the example above can be stated in any files as long as you are sure it is read at the run-time and after Devise Ruby code. application_controller.rb is one of the sure places (though I guess there is a better-fitting place). Make sure the sentence is put out of the class ApplicationController block.

Is this vulnerable to mass assignment?

I use this to allow users to vote on an Entry:
<% form_tag url_for(entry_votes_path(#entry)), :id => 'voting_form', :remote => true do %>
<%= hidden_field_tag 'vote[user_id]', current_user.id %>
<%= submit_tag 'Vote for this entry', :id => 'voting_button' %>
<% end %>
This is my controller code:
def create
#entry = Entry.find(params[:entry_id])
#vote = #entry.votes.build(params[:vote])
respond_to do |format|
if #vote.save
format.html { redirect_to #entry }
format.js
end
end
end
I have two questions
How can I assign current_user.id without using a hidden field?
Also, I'm not using attr_accessible or attr_protected on the Vote model right now. How should I secure the model to make sure someone can't create a lot of votes? Right now, all the fields in the Vote model are set by the params hash -- should I use attr_protected on say, the entry_id foreign key and then set it separately in the controller?
I'm not using attr_accessible
or attr_protected on the Vote model
right now...
Then, by definition, mass assignment is possible from the query string.
Should I use attr_protected on say,
the entry_id foreign key and then set
it separately in the controller?
In general, it's better to use attr_accessible than attr_protected. This is because attr_accessible establishes a default of deny-all to mass assignment and lets you define whitelisted exceptions. On the other hand, attr_protected forces you to blacklist specific attributes. When you modify the program and add a new attribute with attr_accessible set, the program will fail if you need to whitelist the attribute and forget. In other words, it fails safely. Alternatively, if you add a new attribute with attr_protected set, the program will work even if the new attribute should have been included in the blacklist. On other words, it fails insecurely.
The rule here is to protect any attribute which it would be dangerous to allow to be set from the query string. Protecting the key helps prevent injection of new rows but it may be necessary to protect other fields if you want to prevent the ability to change the contents of existing rows.
A good reference on this may be found at guides.rubyonrails.org.