Handling permalink user update when permalink is already taken Rails 3.2 - ruby-on-rails

I wanted some advice about how to handle to_param in regards to permalinks
Basically this is what happens.
Create a new company
The company :name is then parameterized and saved as a :permalink in the db
Updating an existing company enables you to change the :permalink
There are validations to ensure user updated :permalink is unique
The problem I'm having is occurring when updating the company's :permalink to something that already exists. The uniqueness validation works which is great, but it changes the params[:id] to the invalid permalink instead of reseting and using the existing params[:id]
When I try to edit the permalink to something else I get a flash validation error of "Name already taken" because it thinks I'm editing the company of the already existing :permalink (company). The URL reflects the change in permalink since my companies_controller.rb is using #company = Company.find_by_permalink[:id])
I wanted to know the best way to handle this issue?
class Companies < ActiveRecord::Base
before_create :set_permalink
before_update :update_permalink
attr_accessible :name, :permalink
validates :name, :permalink, uniqueness: { message: 'already taken' }
def to_param
permalink
end
private
def set_permalink_url
self.permalink = name.parameterize
end
def update_permalink_url
self.permalink = permalink.parameterize
end
end
Apologies if I'm not making too much sense.
Thanks in advance.

you could try to handle this with an after_rollback callback.
after_rollback :restore_permalink
def restore_permalink
self.permalink = permalink_was if permalink_changed?
end
here's how it works : every update / destroy in Rails is wrapped in a transaction. If the save fails, the transaction rollbacks and triggers the callback.
The callback then restores the old value (permalink_was) if it was changed since the record has been loaded.
See ActiveModel::Dirty and ActiveRecord::Transactions for more info.
EDIT
On the other hand, there may be another solution (untested) - just define your accessor like this :
def permalink=( value )
permalink_will_change! unless #permalink == value
#permalink = value
end
This way, the permalink will not be marked as dirty if the new value is identical to the old one, and so AR will not try to update the column.
Explanation:
i don't know on which version of rails it was implemented (it is relatively recent), but here's how "dirtyness" works :
your "standard" (automagically generated) attribute setters basicly call
#{your_attribute}_will_change! before setting the associated
instance variable (even if you set the exact same value than before)
when you call save, ActiveRecords looks for attributes that have changed ("dirty") and builds the SQL UPDATE query using ONLY these attributes (for performance reasons, mostly)
so if you want to avoid your permalink to appear in the query when it is unchanged, i think you have to override the standard setter - or avoid mass-assignment and only set permalink if it has changed

Related

How to stop create in rails 5 without rolling back changes?

I need to check if a similar record exist in database before save, if true then update the existing record without saving, else create a new one
In rails 5:
returning false in a hook method doesn't halt callbacks and "throw :abort" is used instead.
the problem is using "throw :abort" rolls back any changes made in the before_save callback.
what I am trying to do is to check for a similar recored in "before_save" and if a similar record exist I need to update the current record and stop saving the new one.
I used
before_save :check
def check
if (similar record exist..)
update current...
return false <==========
end
true
end
but this is not working any more in Rails 5 so returning false doesn't stop it from saving the new record too.
and I tried
before_save :check
def check
if (exist..)
update current...
throw :abort <========
end
true
end
this stops saving current record to db but it perform "rollback" so the updated recored is missed !!
how can I do that ?
I think this is one possible way. This example if with a Product model looking for same name.
before_create :rollback_if_similar_exists
after_rollback :update_existing_record
def rollback_if_similar_exists
throw :abort if Product.exists? name: self.name
end
def update_existing_record
# do here what you need
puts name
puts "find the existing record"
puts "update data"
end
Here is a slightly different approach you could take:
Instead of using before_save, create your own validation and use assign_attributes instead of update or create since assign_attributes won't actually write to the database. You could then call the valid? function on your record to execute your validations. If you get a duplicate record error from the validation you defined, then have your code handle updating the existing record in the logic of your error handling.
Something like this in your controller:
#record.assign_attributes(my_parameter: params[:my_parameter])
if #record.valid?
#record.save
else
puts #record.errors.messages.inspect
#update your existing record instead.
end
Then in your model:
validate :my_validation
def my_validation
if record_already_exists
return errors.add :base, "Your custom error message here."
end
end
I'd recommend using #find_or_initialize_by or #find_or_create_by to instantiate your instances. Instead of placing record swapping code inside a callback. This means you'll do something like this (example controller create):
class Post < ApplicationController
def create
#post = Post.find_or_initialize_by(title: param[:title])
if #post.update(post_params)
redirect_to #post
else
render :new
end
end
end
Pair this with a validation that doesn't allow you to create double records with similar attributes and you're set.
create_table :posts do |t|
t.string :title, null: false
t.text :body
end
add_index :posts, :title, unique: true
class Post < ApplicationRecord
validates :title, presence: true, uniqueness: true
end
I don't recommend the following code, but you could set the id of your instance to match the record with similar data. However you'll have to bypass persistence (keeps track of new and persistent records) and dirty (keeps track of attribute changes). Otherwise you'll create a new record or update the current id instead of the similar record id:
class Post < ApplicationRecord
before_save :set_similar_id
private
def set_similar_id
similar_record = Post.find_by(title: title)
return unless similar_record
#attributes['id'].instance_variable_set :#value, similar_record.id
#new_record = false
end
end
Keep in mind that only changes are submitted to the database when creating a new record. For new records these are only the attributes of which the attributes are set, attributes with value nil are not submitted and will keep their old value.
For existing records theses are the attributes that are not the same as there older variant and the rule old_value != new_value (not actual variable names) applies.

In Rails, is it possible to repair model validation errors inside the model?

Let's say a model catches a validation error, usually this is handled by the controller, but is it possible to handle it automatically by the model?
Practically I want to generate a unique id uid for each Note, the model looks like this:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
end
The closest I got is:
class Note < ActiveRecord::Base
validates_uniqueness_of :uid
# ... some code to generate uid on after_initialize
after_rollback :repair
protected
def repair
if self.errors[:uid].size > 0
self.uid = generate_uid
end
self.save # Try again
end
end
Some immediate problems with my solution: (1) The model instance still has errors that the controller can see, I'm not sure how to clear the errors. (2) The repair method is recursive.
While I'm sure there is a way to catch and handle the errors in the model (maybe the after_validation callback could be of use), perhaps you can avoid the issue in this case by ensuring that the uid you generate is unique when you create it.
Ryan Bates offered this method for generating unique tokens in a RailsCast:
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
With the use of a before_create callback, i.e. before_create { generate_token(:uid) }, each model will have a unique id.
All this said, #Beerlington raises a really good point about UUIDs.
Update: Note that the method given is expecting to be defined in a User model. For your example, you'd want to change it to ...while Note.exists?....
I would use a true UUID that is guaranteed to be unique, and not add the overhead to your model. Having a uniqueness validation in the model adds some overhead because it has to hit the database to figure out if something exists, and it's still not even guaranteed.
Check out this Ruby project to generate UUIDs: https://github.com/assaf/uuid/

Difference between attr_accessor and attr_accessible

In Rails, what is the difference between attr_accessor and attr_accessible? From my understanding, using attr_accessor is used to create getter and setter methods for that variable, so that we can access the variable like Object.variable or Object.variable = some_value.
I read that attr_accessible makes that specific variable accessible to the outside world.
Can someone please tell me whats the difference
attr_accessor is a Ruby method that makes a getter and a setter. attr_accessible is a Rails method that allows you to pass in values to a mass assignment: new(attrs) or update_attributes(attrs).
Here's a mass assignment:
Order.new({ :type => 'Corn', :quantity => 6 })
You can imagine that the order might also have a discount code, say :price_off. If you don't tag :price_off as attr_accessible you stop malicious code from being able to do like so:
Order.new({ :type => 'Corn', :quantity => 6, :price_off => 30 })
Even if your form doesn't have a field for :price_off, if it's in your model it's available by default. This means a crafted POST could still set it. Using attr_accessible white lists those things that can be mass assigned.
Many people on this thread and on google explain very well that attr_accessible specifies a whitelist of attributes that are allowed to be updated in bulk (all the attributes of an object model together at the same time)
This is mainly (and only) to protect your application from "Mass assignment" pirate exploit.
This is explained here on the official Rails doc : Mass Assignment
attr_accessor is a ruby code to (quickly) create setter and getter methods in a Class. That's all.
Now, what is missing as an explanation is that when you create somehow a link between a (Rails) model with a database table, you NEVER, NEVER, NEVER need attr_accessor in your model to create setters and getters in order to be able to modify your table's records.
This is because your model inherits all methods from the ActiveRecord::Base Class, which already defines basic CRUD accessors (Create, Read, Update, Delete) for you.
This is explained on the offical doc here Rails Model and here Overwriting default accessor (scroll down to the chapter "Overwrite default accessor")
Say for instance that: we have a database table called "users" that contains three columns "firstname", "lastname" and "role" :
SQL instructions :
CREATE TABLE users (
firstname string,
lastname string
role string
);
I assumed that you set the option config.active_record.whitelist_attributes = true in your config/environment/production.rb to protect your application from Mass assignment exploit. This is explained here : Mass Assignment
Your Rails model will perfectly work with the Model here below :
class User < ActiveRecord::Base
end
However you will need to update each attribute of user separately in your controller for your form's View to work :
def update
#user = User.find_by_id(params[:id])
#user.firstname = params[:user][:firstname]
#user.lastname = params[:user][:lastname]
if #user.save
# Use of I18 internationalization t method for the flash message
flash[:success] = t('activerecord.successful.messages.updated', :model => User.model_name.human)
end
respond_with(#user)
end
Now to ease your life, you don't want to make a complicated controller for your User model.
So you will use the attr_accessible special method in your Class model :
class User < ActiveRecord::Base
attr_accessible :firstname, :lastname
end
So you can use the "highway" (mass assignment) to update :
def update
#user = User.find_by_id(params[:id])
if #user.update_attributes(params[:user])
# Use of I18 internationlization t method for the flash message
flash[:success] = t('activerecord.successful.messages.updated', :model => User.model_name.human)
end
respond_with(#user)
end
You didn't add the "role" attributes to the attr_accessible list because you don't let your users set their role by themselves (like admin). You do this yourself on another special admin View.
Though your user view doesn't show a "role" field, a pirate could easily send a HTTP POST request that include "role" in the params hash. The missing "role" attribute on the attr_accessible is to protect your application from that.
You can still modify your user.role attribute on its own like below, but not with all attributes together.
#user.role = DEFAULT_ROLE
Why the hell would you use the attr_accessor?
Well, this would be in the case that your user-form shows a field that doesn't exist in your users table as a column.
For instance, say your user view shows a "please-tell-the-admin-that-I'm-in-here" field.
You don't want to store this info in your table. You just want that Rails send you an e-mail warning you that one "crazy" ;-) user has subscribed.
To be able to make use of this info you need to store it temporarily somewhere.
What more easy than recover it in a user.peekaboo attribute ?
So you add this field to your model :
class User < ActiveRecord::Base
attr_accessible :firstname, :lastname
attr_accessor :peekaboo
end
So you will be able to make an educated use of the user.peekaboo attribute somewhere in your controller to send an e-mail or do whatever you want.
ActiveRecord will not save the "peekaboo" attribute in your table when you do a user.save because she don't see any column matching this name in her model.
attr_accessor is a Ruby method that gives you setter and getter methods to an instance variable of the same name. So it is equivalent to
class MyModel
def my_variable
#my_variable
end
def my_variable=(value)
#my_variable = value
end
end
attr_accessible is a Rails method that determines what variables can be set in a mass assignment.
When you submit a form, and you have something like MyModel.new params[:my_model] then you want to have a little bit more control, so that people can't submit things that you don't want them to.
You might do attr_accessible :email so that when someone updates their account, they can change their email address. But you wouldn't do attr_accessible :email, :salary because then a person could set their salary through a form submission. In other words, they could hack their way to a raise.
That kind of information needs to be explicitly handled. Just removing it from the form isn't enough. Someone could go in with firebug and add the element into the form to submit a salary field. They could use the built in curl to submit a new salary to the controller update method, they could create a script that submits a post with that information.
So attr_accessor is about creating methods to store variables, and attr_accessible is about the security of mass assignments.
attr_accessor is ruby code and is used when you do not have a column in your database, but still want to show a field in your forms. The only way to allow this is to attr_accessor :fieldname and you can use this field in your View, or model, if you wanted, but mostly in your View.
Let's consider the following example
class Address
attr_reader :street
attr_writer :street
def initialize
#street = ""
end
end
Here we have used attr_reader (readable attribute) and attr_writer (writable attribute) for accessing purpose. But we can achieve the same functionality using attr_accessor. In short, attr_accessor provides access to both getter and setter methods.
So modified code is as below
class Address
attr_accessor :street
def initialize
#street = ""
end
end
attr_accessible allows you to list all the columns you want to allow Mass Assignment. The opposite of this is attr_protected which means this field I do NOT want anyone to be allowed to Mass Assign to. More than likely it is going to be a field in your database that you don't want anyone monkeying around with. Like a status field, or the like.
In two words:
attr_accessor is getter, setter method.
whereas attr_accessible is to say that particular attribute is accessible or not. that's it.
I wish to add we should use Strong parameter instead of attr_accessible to protect from mass asignment.
Cheers!
A quick & concise difference overview :
attr_accessor is an easy way to create read and write accessors in
your class. It is used when you do not have a column in your database,
but still want to show a field in your forms. This field is a
“virtual attribute” in a Rails model.
virtual attribute – an attribute not corresponding to a column in the database.
attr_accessible is used to identify attributes that are accessible
by your controller methods makes a property available for
mass-assignment.. It will only allow access to the attributes that you
specify, denying the rest.

How to set some field in model as readonly when a condition is met?

I have models like this:
class Person
has_many :phones
...
end
class Phone
belongs_to :person
end
I want to forbid changing phones associated to person when some condition is met. Forbidden field is set to disabled in html form. When I added a custom validation to check it, it caused save error even when phone doesn't change. I think it is because a hash with attributes is passed to
#person.update_attributes(params[:person])
and there is some data with phone number (because form include fields for phone). How to update only attributes that changed? Or how to create validation that ignore saves when a field isn't changing? Or maybe I'm doing something wrong?
You might be able to use the
changed # => []
changed? # => true|false
changes # => {}
methods that are provided.
The changed method will return an array of changed attributes which you might be able to do an include?(...) against to build the functionality you are looking for.
Maybe something like
validate :check_for_changes
def check_for_changes
errors.add_to_base("Field is not changed") unless changed.include?("field")
end
def validate
errors.add :phone_number, "can't be updated" if phone_number_changed?
end
-- don't know if this works with associations though
Other way would be to override update_attributes, find values that haven't changed and remove them from params hash and finally call original update_attributes.
Why don't you use before_create, before_save callbacks in model to restrict create/update/save/delete or virtually any such operation. I think hooking up observers to decide whether you want to restrict the create or allow; would be a good approach. Following is a short example.
class Person < ActiveRecord::Base
#These callbacks are run every time a save/create is done.
before_save :ensure_my_condition_is_met
before_create :some_other_condition_check
protected
def some_other_condition_check
#checks here
end
def ensure_my_condition_is_met
# checks here
end
end
More information for callbacks can be obtained here:
http://guides.rubyonrails.org/activerecord_validations_callbacks.html#callbacks-overview
Hope it helps.

In Rails, How do I validates_uniqueness_of :field with a scope of last 6 months

First Item
I Want to validate a field to make sure it is unique (in the last 6 months) before saving it to the database.
I am thinking I should use validates_uniqueness_of :field, case_sensitive => false, Scope => ...
For my application it only has to be unique if, it was used <6 months ago.
Thinking to compare it to created_at, but don't really know how to go about it.
Second Item
I think I should somehow use .strip to remove any spaces before or after the text that the use may have put in accidentally (I know that these extra spaces are used by default in rails and if they are there can make a filed unique.)
If anyone has any hints on how this should be done correctly I really would appreciate it.
validates_uniqueness_of works by checking if a record already exists with the same value of the given field within the given scope. :scope lets you define the scope (obviously) of the uniqueness; for instance, if I was creating blog software and wanted to only allow a post title to be used once per blog, I could say validates_uniqueness_of :title, :scope => :blog_id -- without the scope, I'd only be allowing each title to be used once across the entire system. :scope won't let you do a complex check, like that which you desire.
What you're probably need to do is create your own validation function to check the uniqueness of the field in question within the given timeframe (code goes within the model):
validate :field_must_be_unique_within_six_months
def field_must_be_unique_within_six_months
return if field.blank?
num_duplicates = self.class.count(:conditions => ["field = ? AND created_at < ?", self.field, 6.months.ago])
if num_duplicates > 0
errors.add(:field, :taken)
end
end
The field_must_be_unique_within_six_months method will work similarly to validates_uniqueness_of, in that it will add an error message if there is already a record with the same given field, but with the added condition that it will also check the date. The validate :field_must_be_unique_within_six_months will add the method to the validation process when a record is saved.
To validate multiple fields at the same time without violating DRY, you could use validates_each to do something like the following:
validates_each :field1, :field2 do |record, attr, value|
if record.class.exists?(["#{attr.to_s} = ? AND created_at < ?", value, 6.months.ago])
errors.add(attr, :taken)
end
end
In the above block, record is the record being validated, attr is the attribute (so field1, field2, etc.) and value is the value of that attribute.
You can probably do something like this:
def validate
errors.add(:field, 'blah blah') if is_used_recently && !has_unique_field?
end
def has_unique_field?
Model.exists?(['field = ? and created_at > ?', self.field, 6.months.ago])
end
def is_used_recently
self.created_at < 6.months.ago || self.new? # i don't know if created_at would be set by this point
end
Alternatively you might want to create a new validation handler, or extend the existing one to pass in a :within option if that's something you're going to be doing often.
To get rid of leading and trailing white space the method you want is 'strip'. You can run this on all your fields by doing something like:
before_validation :clean_up_whitespace
def clean_up_whitespace
self.some_field.strip! # this does the strip in place
end
I hope this helps, let me know if I've made any mistakes!

Resources