With the help of some here, I'm using this code to generate tokens purchased from my app. It loops through depending on how many tokens are ordered, calls a method called create_trackable_token which just generates a unique string to identify the token. I have two questions about this.
1) How can I rewrite it so the particular columns are not vulnerable to mass-assignment? Right now, if I use 'attr_accessible` on this model, I have to expose the three attributes in this method because it's assigning them at once. I'd prefer to not do that. The method is already protected, so it can't be called by an end user.
2) What's the best way of handling errors? The tokens have to be unique, and right now, I'm not sure what would happen if the create_trackable_token method generates a string that's already in use. Does ActiveRecord take care of that or do I need to write some error handling into the method?
protected
def create_trackables
return unless self.success
order = Order.find(order_id) #you shouldn't need this line if it has_one :order
1.upto(order.total_tokens) do
Tracker.create!(
:user_id => order.user_id,
:token => Tracker.create_trackable_token,
:order_id => order_id
)
end
end
To handle uniqueness, use a validation on the column, see http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
When creating the token, there are many things to consider, such as how hard do you want it to be to guess (or else do you authenticate a token against something else?) ... you could use hashes, such ass MD5, random number, or you could go all out and make a GUID. In any case, you could look for the token before returning from create_trackable_token.
Tracker.where(:token => generated_token).exists?
If you want to keep it mass assignment safe you have to do this in a few steps:
t = Tracker.create(
:user_id => order.user_id,
:order_id => order_id
)
t.token = Tracker.create_trackable_token
t.save!
Related
I am creating a messaging system in which a particular user may be a member of one or more organizations. Therefore, if they are signed in to an organization they should only be able to see conversations with users from the same organization but I can't seem to find a way to figure out how to specify that in the query. For example:
recipients = current_org.users
#conversations = current_user.mailbox.inbox.conversations.where(participants.include?(recipients))
..or something along those lines.
I didn't find a great way to do this, but this is what I did for future reference. I added a class_eval in an initializer file on Mailboxer's Receipts model, creating a slightly modified version of their .recipients scope. It checks to see if there is more than one in the collection and calls .id on it, which was an issue before. It also gets the base_class of the first of the collection since they will all be the same.
Mailboxer::Receipt.class_eval do
scope :recipients, lambda { |recipient|
if recipient.is_a?(ActiveRecord::Associations::CollectionProxy)
where(:receiver_id => recipient.collect {|x| x.id },:receiver_type => recipient.first.class.base_class.to_s)
else
where(:receiver_id => recipient.id,:receiver_type => recipient.class.base_class.to_s)
end
}
end
I'd like to use an if statement to check if the value in a Postgres DB table is unique. If unique, then do something, if not unique, do something else. Here's what the pseudo code would look like in Ruby on Rails.
if validates_uniqueness_of :number == "true"
puts "this value is unique and should be added to the DB"
else
puts "this value is not unique and should not be added to the DB"
end
Can this type of logic be implemented in the model or controller? If yes, which is the better way to go? If no, what should I do instead? Also, what would the syntax look for something like this?
Thanks guys!
You can use the exists? method to check whether a record is in the database already. It can take a hash of fields you want to search on:
before_create :do_something_if_unique
def do_something_if_unique
if self.class.exists?(number: number)
# there is a record that exists with this number
else
# there are no records that exist with this number
end
end
In order to found unique values in a column, I'd do something like:
def self.has_unique_numbers?
pluck(:number).uniq.count == 1
end
Then in your model or controller, you can ask:
if YourModel.has_unique_numbers?
# Some Code
else
# Some other code
end
You can use first_or_create:
MyModel.where(number: number).first_or_create!
If nothing is found matching the given criteria (more AR methods can be chained in as well), the object is saved to the database. Whether it should go in the model or controller depends on how you are using it. It should work fine in either.
Take a look at :validates_uniqueness_of for model validations. Whether or not you want to additionally add database constraints is a bigger issue open to a lot of debate depending on what side of the fence you sit on :)
class MyThing < ActiveRecord::Base
validates :number, :uniqueness => true
end
a = MyThing.create(:number => 1) # will succeeed
b = MyThing.create(:number => 2) # will fail and a.errors will contain more info.
I'm currently using Rails 2.3.9. I understand that specifying the :joins option in a query without an explicit :select automatically makes any records that are returned read-only. I have a situation where I would like to update the records and while I've read about different ways to approach it, I was wondering which way is the preferred or "proper" way.
Specifically, my situation is that I have the following User model with an active named scope that performs a JOIN with the subscriptions table:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
When I call User.active.all, the user records that are returned are all read-only, so if, for instance, I call update_attributes! on a user, ActiveRecord::ReadOnlyRecord will be raised.
Through reading various sources, it seems a popular way to get around this is by adding :readonly => false to the query. However, I was wondering the following:
Is this safe? I understand the reason why Rails sets it to read-only in the first place is because, according to the Rails documentation, "they will have attributes that do not correspond to the table’s columns." However, the SQL query that is generated from this call uses SELECT `users`.* anyway, which appears to be safe, so what is Rails trying to guard against in the first place? It would appear that Rails should be guarding against the case when :select is actually explicitly specified, which is the reverse of the actual behavior, so am I not properly understanding the purpose of automatically setting the read-only flag on :joins?
Does this seem like a hack? It doesn't seem proper that the definition of a named scope should care about explicitly setting :readonly => false. I'm also afraid of side effects if the named scoped is chained with other named scopes. If I try to specify it outside of the scope (e.g., by doing User.active.scoped(:readonly => false) or User.scoped(:readonly => false).active), it doesn't appear to work.
One other way I've read to get around this is to change the :joins to an :include. I understand the behavior of this better, but are there any disadvantages to this (other than the unnecessary reading of all the columns in the subscriptions table)?
Lastly, I could also retrieve the query again using the record IDs by calling User.find_all_by_id(User.active.map(&:id)), but I find this to be more of a workaround rather than a possible solution since it generates an extra SQL query.
Are there any other possible solutions? What would be the preferred solution in this situation? I've read the answer given in the previous StackOverflow question about this, but it doesn't seem to give specific guidance of what would be considered correct.
Thanks in advance!
I believe that it would be customary and acceptable in this case to use :include instead of :join. I think that :join is only used in rare specialized circumstances, whereas :include is pretty common.
If you're not going to be updating all of the active users, then it's probably wise to add an additional named scope or find condition to further narrow down which users you're loading so that you're not loading extra users & subscriptions unnecessarily. For instance...
User.active.some_further_limiting_scope(:with_an_argument)
#or
User.active.find(:all, :conditions => {:etc => 'etc'})
If you decide that you still want to use the :join, and are only going to update a small percentage of the loaded users, then it's probably best to reload just the user you want to update right before doing so. Such as...
readonly_users = User.active
# insert some other code that picks out a particular user to update
User.find(readonly_users[#index].id).update_attributes(:etc => 'etc')
If you really do need to load all active users, and you want to stick with the :join, and you will likely be updating most or all of the users, then your idea to reload them with an array of IDs is probably your best choice.
#no need to do find_all_by_id in this case. A simple find() is sufficient.
writable_users_without_subscriptions = User.find(Users.active.map(&:id))
I hope that helps. I'm curious which option you go with, or if you found another solution more appropriate for your scenario.
I think the best solution is to use .join as you have already and do a separate find()
One crucial difference of using :include is that it uses outer join while :join uses an inner join! So using :include may solve the read-only problem, but the result might be wrong!
I ran across this same issue and was not comfortable using :readonly => false
As a result I did an explicit select namely :select => 'users.*' and felt that it seemed like less of a hack.
You could consider doing the following:
class User < ActiveRecord::Base
has_one :subscription
named_scope :active, :select => 'users.*', :conditions => { :subscriptions => { :status => 'active' } }, :joins => :subscription
end
Regarding your sub-question: so am I not properly understanding the purpose of automatically setting the read-only flag on :joins?
I believe the answer is: With a joins query, you're getting back a single record with the User + Subscription table attributes. If you tried to update one of the attributes (say "subscription_num") in the Subscription table instead of the User table, the update statement to the User table wouldn't be able to find subscription_num and would crash. So the join-scopes are read-only by default to prevent that from happening.
Reference:
1) http://blog.ethanvizitei.com/2009/05/joins-and-namedscopes-in-activerecord.html
One of the models in a Rails 3.1 application I'm working on has a "code" attribute that is generated automatically when the record is created and that must be unique. The application should check the database to see if the generated code exists and, if it does, it should generate a new code and repeat the process.
I can ensure the field's uniqueness at the database level with add_index :credits, :code, :unique => true (which I am doing) and also in the model with validates_uniqueness_of, but both of these will simply return an error if the generated code exists. I need to just try again in the case of a duplicate. The generated codes are sufficiently long that duplicates are unlikely but I need to be 100% certain.
This code generation is handled transparently to the end user and so they should never see an error. Once the code is generated, what's the best way to check if it exists and to repeat the process until a unique value is found?
Here's a quick example, there is still technically a race condition here, though unless your seeing hundreds or thousands of creates per second it really shouldnt be a worry, worst case is your user gets a uniquness error if two creates are run in such a way that they both execute the find and return nil with the same Url
class Credit < ActiveRecord::Base
before_validation :create_code, :if => 'self.new_record?'
validates :code, :uniqueness => true
def create_code
self.code = code_generator
self.code = code_generator until Credit.find_by_code(code).nil?
end
end
If you absolutely needed to remove the race condition case where two creates are running in tandem and both trigger the find with the same code and return nil you could wrap the find with a table lock which requires DB specific SQL, or you could create a table that has a row used for locking on via pessimistic locking, but I wouldn't go that far unless your expecting hundreds of creates per second and you absolutely require that the user never ever sees an error, it's doable, just kind of overkill in most cases.
I am not sure if there is a built in way. I have always used a before_create.
Here is an example in the context of a UrlShortener.
class UrlShortener < Activerecord::Base
before_create :create_short_url
def create_short_url
self.short_url = RandomString.generate(6)
until UrlShortener.find_by_short_url(self.short_url).nil?
self.short_url = RandomString.generate(6)
end
end
end
In one of my model objects I have an array of objects.
In the view I created a simple form to add additional objects to the array via a selection box.
In the controller I use the append method to add user selected objects to the array:
def add_adjacents
#site = Site.find(params[:id])
if request.post?
#site.adjacents << Site.find(params[:adjacents])
redirect_to :back
end
end
I added a validation to the model to validate_the uniqueness_of :neighbors but using the append method appears to be bypassing the validation.
Is there a way to force the validation? Or a more appropriate way to add an element to the array so that the validation occurs? Been googling all over for this and going over the books, but can't find anything on this.
Have you tried checking the validity afterwards by calling the ".valid?" method, as shown below?
def add_adjacents
#site = Site.find(params[:id])
#site.neighbors << Site.find(params[:neighbors])
unless #site.valid?
#it's not valid, do something to fix it!
end
end
A couple of comments:
Then only way to guarantee uniqueness is to add a unique constraint on your database. validates_uniqueness_of has it's gotchas when there are many users in the system:
Process 1 checks uniqueness, returns true.
Process 2 checks uniqueness, returns true.
Process 1 saves.
Process 2 saves.
You're in trouble.
Why do you have to test for request.post?? This should be handled by your routes, so in my view it's logic that is fattening your controller unnecessarily. I'd imagine something like the following in config/routes.rb: map.resources :sites, :member => { :add_adjacents => :post }
Need to know more about your associations to figure out how validates_uniqueness_of should play in with this setup...
I think you're looking for this:
#site.adjacents.build params[:adjacents]
the build method will accept an array of attribute hashes. These will be validated along with the parent model at save time.
Since you're validating_uniqueness_of, you might get some weirdness when you are saving multiple conflicting records at the same time, depending on the rails implementation for the save and validation phases of the association.
A hacky workaround would be to unique your params when they come in the door, like so:
#site.adjacents.build params[:adjacents].inject([]) do |okay_group, candidate|
if okay_group.all? { |item| item[:neighbor_id] != candidate[:neighbor_id] }
okay_group << candidate
end
okay_group
end
For extra credit you can factor this operation back into the model.