Maximum number of associated records - sql

I'd like to have a maximum number of associated records on a model.
E.g. a project has_many tasks, but not more then twenty.
How can I enforce this rule?
The only solution that I've been able to come up with so far is an
INSERT INTO...SELECT query like this:
INSERT INTO
tasks (`id`,`project_id`,`title`,`body`)
SELECT
NULL, ?, ?, ?
FROM
tasks
HAVING
count(id) < MAX_NUMBER_OF_TASKS
LIMIT 1;
As far as I can tell, this will guarantee a maximum number of tasks being inserted. Am I correct in this?
Is there a 'Rails way' to do this?
Is it possible to override ActiveRecord/the Task model so that it uses the query above
to insert a new record?
I'm currently using a custom method with ActiveRecord::Base.connection
and calling that instead of .create or .save when new_record? == true.

I haven't been able to try this, but I can't see why it shouldn't work.
Step one: Define a validator on the parent object (this is a simple implementation - could/should be made more generic):
class Project < ActiveRecord::Base
validate :max_tasks
def max_tasks
if tasks.count > 20
errors.add_to_base("Should not have more than 20 tasks")
end
end
end
Step two: Turn on validation of project from tasks:
class Task < ActiveRecord::Base
validates_associated :project
end
And I think you should be in business. When you try and save a new task, it'll validate the associated project, and the validation will fail if there are (now) more than 20 tasks associated.
Just in case you fancy making this more generic, you could do something like:
class NumberOfAssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if options[:maximum] && record.send(attribute).count > options[:maximum]
record.errors[attribute] << "must not have more than #{options[:maximum]}"
end
if options[:minimum] && record.send(attribute).count < options[:minimum]
record.errors[attribute] << "must not have less than #{options[:minimum]}"
end
end
end
class MyModel < ActiveRecord::Base
validates :my_association, :number_of_associated => {:maxiumum => 20}
end

May be you can add some pre save validation to your model, that checks how many associated models it already have, and throw a validation error if it exceeds your max number of associations.

Related

What's a reliable why to create a custom id for an associated item in rails

I have a nested resource like this
resources :projects do
resources :tasks
end
The tasks have a field named number. Whenever I create a task I would like to give it a squential number within the parent project.
This is my model class
class Task < ActiveRecord :: Base
belongs_to :project
validate_presence_of :title
before_create :generate_number
private
def generate_number
if project.tasks.nil? || project.tasks.count < 1
self.number = 1
else
self.number = list.topics.count+1
end
end
end
I am not sure about certain things:
Does this logic belongs in my Task Model or in my Project model or in a seperate class/module?
What is the best before filter. (before_create, before_validation, validation)?
Because there are many ways how to create a task. With a list, in a list, alone and then attach it to a list...
And which filter would work in my tests so that I could setup some Fakes for example with factory girl... Because right now FactoryGirl does not always executes generate number...
This is my factory
FactoryGirl.define do
factory :project do
name "Hello world"
end
trait :with_tasks do
ignore do
number_of_tasks 3
end
after :create do |project,evaluator|
#project.Factory.create_list :taks, evaluator.number_of_tasks, :project => project
end
end
end
What would be the best. reliable way to generate a sequential custom taks number depending on the project which works in my specs as well as in production?
Any best practise tips would be appreciated.
I would keep the before_create callback in the Task model, which would call the generate_number function. This should work in Factory girl where it would add the number if you use Factory.create, but not when you use Factory.build.

ruby validation: limiting number of objects for one object based on time (has_many)

I am programming a booking system.
I want my users to be able to book only one (or a defined number of) resources at a time. However, I do not want to remove "past" reservation for my database, since it will be used for invoicing purposes. On reservation creation, I need to validate that a user has not exceeded its reservation quota, which means has not more than "quota" reservations in the future.
class User < ActiveRecord::Base
has_many :reservations
def active_reservations
#maybe worth to rewrite with a "find"?
my_list = []
reservations.each do |reservation|
if (not reservation.past?)
my_list.push(reservation)
end
return my_list
end
class Reservation < ActiveRecord::Base
belongs_to :user
validate :respect_user_quota
def past?
return (date < Date.now)
def respect_user_quota
if (user.active_reservations.count > user.quota)
errors.add(:user, "User quota exceeded!")
Is this the right way to implement this validation? What could be wrong there (I never see the error message). Should the quota validation be moved to the user class?
I would try and do this more simply and move the validation to user.
class User < ActiveRecord::Base
has_many :reservations
validate :reservation_quota
if sum(reservations.active) > quota # User.quota is implied here
errors.add(:user, "User quota exceeded!")
end
class Reservation < ActiveRecord::Base
belongs_to :user
def active
active? 1 : 0
# If there's a boolean 'active' flag the ? method gets created automatically.
# This could be (reservation_date < Date.now)? ? 1 : 0 for you.
# Using `(expression)? ? true : false` is using the Ternary operator.
end
end

Rails: Avoiding duplication errors in Factory Girl...am I doing it wrong?

Suppose I have a model user, which has a uniqueness constraint on the email field
If I call Factory(:user) once all is well, but if I call it a second time it'll fail with an "entry already exists" error.
I'm currently using a simple helper to search for an existing entry in the DB before creating the factory...and calling any factory I make through that helper.
It works, but it's not entirely elegant, and considering how common I assume this problem must be, I'm guessing there's a better solution. So, is there an inbuilt way in factory girl to return_or_create a factory, instead of just charging ahead with create()? If not, how do most folk avoid duplicate entries with their factories?
Simple answer: use factory.sequence
If you have a field that needs to be unique you can add a sequence in factory_girl to ensure that it is never the same:
Factory.define :user do |user|
sequence(:email){|n| "user#{n}#factory.com" }
user.password{ "secret" }
end
This will increment n each time in order to produce a unique email address such as user52#factory.com. (See https://github.com/thoughtbot/factory_girl/wiki/Usage for more info)
However this isn't always great in Rails.env.development...
Over time I have found that this is not actually the most useful way to create unique email addresses. The reason is that while the factory is always unique for your test environment it's not always unique for your development environment and n resets itself as you start the environment up and down. In :test this isn't a problem because the database is wiped but in :development you tend to keep the same data for a while.
You then get collisions and find yourself having to manually override the email to something you know is unique which is annoying.
Often more useful: use a random number
Since I call u = Factory :user from the console on a regular basis I go instead with generating a random number. You're not guaranteed to avoid collisions but in practice it hardly ever happens:
Factory.define :user do |user|
user.email {"user_#{Random.rand(1000).to_s}#factory.com" }
user.password{ "secret" }
end
N.B. You have to use Random.rand rather than rand() because of a collision (bug?) in FactoryGirl [https://github.com/thoughtbot/factory_girl/issues/219](see here).
This frees you to create users at will from the command line regardless of whether there are already factory generated users in the database.
Optional extra for making email testing easier
When you get into email testing you often want to verify that an action by a particular user triggered an email to another user.
You log in as Robin Hood, send an email to Maid Marion and then go to your inbox to verify it. What you see in your inbox is something from user_842#factory.com. Who the hell is that?
You need to go back to your database to check whether the email was sent / received by whomever you expected it to be. Again this is a bit of a pain.
What I like to do instead is to generate the email using the name of the Factory user combined with a random number. This makes it far easier to check who things are coming from (and also makes collisions vanishingly unlikely). Using the Faker gem (http://faker.rubyforge.org/) to create the names we get:
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name}_#{u.last_name}_#{Random.rand(1000).to_s}#factory.com" }
end
finally, since Faker sometimes generates names that aren't email-friendly (Mike O'Donnell) we need to whitelist acceptable characters: .gsub(/[^a-zA-Z1-10]/, '')
Factory.define :user do |user|
user.first_name { Faker::Name::first_name }
user.last_name { Faker::Name::last_name }
user.email {|u| "#{u.first_name.gsub(/[^a-zA-Z1-10]/, '')}_#{u.last_name.gsub(/[^a-zA-Z1-10]/, '')}_#{Random.rand(1000).to_s}#factory.com" }
end
This gives us personable but unique emails such as robin_hood_341#factory.com and maid_marion_10#factory.com
Here's what I do to force the 'n' in my factory girl sequence to be the same as that object's id, and thereby avoid collisions:
First, I define a method that finds what the next id should be in app/models/user.rb:
def self.next_id
self.last.nil? ? 1 : self.last.id + 1
end
Then I call User.next_id from spec/factories.rb to start the sequence:
factory :user do
association(:demo)
association(:location)
password "password"
sequence(:email, User.next_id) {|n| "darth_#{n}#sunni.ru" }
end
I found this a nice way to be sure the tests will always pass.
Otherwise you can not be sure the 100% of the times you will create a unique email.
FactoryGirl.define do
factory :user do
name { Faker::Company.name }
email { generate(:email) }
end
sequence(:email) do
gen = "user_#{rand(1000)}#factory.com"
while User.where(email: gen).exists?
gen = "user_#{rand(1000)}#factory.com"
end
gen
end
end
If you only need to generate a few values for attributes, you can also add a method to String, which keeps track of the prior strings used for an attribute. You could then do something like this:
factory :user do
fullname { Faker::Name.name.unique('user_fullname') }
end
I use this approach for seeding. I wanted to avoid sequence numbers, because they do not look realistic.
Here the String extension which makes this happen:
class String
# Makes sure that the current string instance is unique for the given id.
# If you call unique multiple times on equivalent strings, this method will suffix it with a upcounting number.
# Example:
# puts "abc".unique("some_attribute") #=> "abc"
# puts "abc".unique("some_attribute") #=> "abc-1"
# puts "abc".unique("some_attribute") #=> "abc-2"
# puts "abc".unique("other") #=> "abc"
#
# Internal:
# We keep a data structure of the following format:
# ##unique_values = {
# "some_for_id" => { "used_string_1" : 1, "used_string_2": 2 } # the numbers represent the counter to be used as suffix for the next item
# }
def unique(for_id)
##unique_values ||= {} # initialize structure in case this method was never called before
##unique_values[for_id] ||= {} # initialize structure in case we have not seen this id yet
counter = ##unique_values[for_id][self] || 0
result = (counter == 0) ? self : "#{self}-#{counter}"
counter += 1
##unique_values[for_id][self] = counter
return result
end
end
Caution: This should not be used for lots of attributes, since we track all prior strings (optimizations possible).

Eagerloading with scoping in rails3

I have been trying to eager load associations based on some scope in my rails3 app, but could not find any solution.
My app has following models:
class Project
has_many :entries
has_many :to_dos
class ToDo
has_may :entries
has_many :tasks
belongs_to :project
class Task
has_many :entries
belongs_to :to_do
class Entry
belongs_to :project
belongs_to :to_do
belongs_to :task
# options format: {:from_date=>(Date.today-1.week), :to_date=>(Date.today+1.week), :user_id=>60}
scope :filtered_list, lambda { |options|
condition = options[:user_id].nil? ? "true" : "user_id = #{options[:user_id]}"
condition += options[:from_date].nil? ? "" : " AND entry_date >= '#{options[:from_date]}'"
condition += options[:to_date].nil? ? "" : " AND entry_date <= '#{options[:to_date]}'"
where(condition)
}
And in projects#index i have following code to get all projects of an user:
#projects = current_user.projects.includes(:entries, :to_dos =>[:entries, :tasks => :entries])
It fetches all projects of the user, along with eager loading the associations. So when i perform following loop to get all the entries within the project, no new query gets fired.
def all_entries(options)
entries = self.entries
self.to_dos.each do |d|
entries += d.entries
d.tasks.each do |t|
entries += t.entries
end
end
end
As this eager loading fetches all entries, it is way too much data than what I actually needed. So I tried to apply some conditions to the entries eager loaded, but could not find any solution. I was looking for something like:
#projects = current_user.projects.includes(:entries.filtered_list(options), :to_dos =>[:entries.filtered_list(options), :tasks => :entries.filtered_list(options)])
So that only the entries satisfying some conditions get loaded.
Can't we use scoping with eager loading?
Please help me out use eagerloading alongside scoping.
As far as I know, scopes cannot be applied to included associations like this. However, you can specify conditions that should only be applied to the eager loading queries. So with a bit of refactoring, you could have a method that only created the conditions you currently define in your scope:
def self.filter_by(options)
condition = options[:user_id].nil? ? "true" : "entries.user_id = #{options[:user_id]}"
condition += options[:from_date].nil? ? "" : " AND entries.entry_date >= '#{options[:from_date]}'"
condition += options[:to_date].nil? ? "" : " AND entries.entry_date <= '#{options[:to_date]}'
condition
end
or a bit more rubyesque:
def self.filter_by(options)
conditions = []
conditions << "entries.user_id = #{options[:user_id]}" unless options[:user_id].nil?
conditions << "entries.entry_date >= '#{options[:from_date]}'" unless options[:from_date].nil?
conditions << "entries.entry_date <= '#{options[:to_date]}'" unless options[:to_date].nil?
conditions.join(" AND ")
end
and then chain that method to your eager loading:
#projects = current_user.projects.includes(:entries, :to_dos =>[:entries, :tasks => :entries].where(Entry.filter_by(options))
and also reuse it in your scope if you need it independently:
scope :filtered_list, lambda { |options| where(Entry.filter_by(options)) }
Disclaimer: None of this is tested with your actual model definitions, but it works fine with some pretty equivalent ones that I had lying around.
Also note that if the filter options ultimately come from the client side, your condition is vulnerable to SQL injection.
Behind the scenes, Rails uses a JOIN to load the relevant data, so that is something to be aware of. It might be a good thing (a few less queries) or a bad thing (if your indexing is suboptimal). That's probably why the guide has this to say:
Even though Active Record lets you specify conditions on the eager
loaded associations just like joins, the recommended way is to use
joins instead.

Rails Unique Order Field For Create and Update Operations

Edit:
Is it possible to create a unique auto increment field that will be incremented on creates and updates in SQL using Rails (similar to an id field but incremented and re-assigned after an update)? For example:
Create Record A (Value: 1)
Create Record B (Value: 2)
Update Record A (Value: 3)
Update Record B (Value: 4)
I'm trying to setup pull synchronization and need a way to grab all records that have been created or updated since a previous synchronization.
I initially used the 'created_at' and 'updated_at' fields, but found them to be difficult to work with and somewhat inaccurate for partial synchronizations.
Edit:
I'm using Postgresql and Sqlite as my databases, so hopefully a solution exists that will work for both systems.
Edit:
To clarify, I want to pass a single integer to my server from the client (the largest 'sync' integer) and get back all the records created or updated after that record was created or updated.
Ended up adding a sequence integer field to my model and setup the following migration:
class CreateSequence < ActiveRecord::Migration
def self.up
begin
execute "CREATE SEQUENCE sequence"
rescue
end
end
def self.down
begin
execute "DROP SEQUENCE sequence"
rescue
end
end
end
Then, in my model I added:
before_save do
self.sequence = self.class.sequence
end
def self.sequence
s ||= self.connection.select_value("SELECT nextval('sequence') ") rescue nil
s ||= self.connection.select_value("SELECT strftime('%s','now')") rescue nil
return
end
Note: For Sqlite sequences are not supported so instead a selection of an 'epoch' form the database is required. However, this has the negative side effect of causing the sequence to be non-unique for rapid creation. However, in my case this was not an issue.
You could use a before_save callback, like so:
class MyModel < ActiveRecord::Base
before_save :increment
...
protected
def increment
self.revision ||= 1
self.revision += 1
end
end
You could make this more reusable by defining and using a Callback Class.
Another option it to use a Gem/plugin that does automatic versioning (and thus maintains a version field).
Whoops, didn't read too carefully.
If you want to grab the records that have been modified since the last sync you could create a boolean field for determining if the current record was synced. Set it to false by default and set it to false on any edit. That should allow you to pull only the items you need.
There is an id attribute that is provided by default and it auto increments. By default it is an integer, however if you were looking for a guid then let me know and I can point you to some good resources.
As far as pulling records since the last sync you could just grab the last id when you run the sync and use it as a starting value when you sync again.
So... the serial number is not associated with a row so much as a table, right?
class SerialNumber < AR::Base
has_many :thingies
# just has an integer serial number field
end
class Thingie < AR::Base
belongs_to :serial_number # probably want to include this in default scope
before_create :bump_serial
before_save : bump_serial
private
def bump_serial
self.serial_number ||= 0
self.serial_number += 1
end
end
This would appear to handle the cases of create, new/save, and update. But not destroy.
Try using the act_as_versioned gem.
It sets a version field for each record that you could use for synchronization. And I think that this would be a better way to synchronize across clients since you can compare the version on the server and client and synchronize those that are higher on the server.
The docs are here.
And the rubygem page is here.