Customizing amistad : adding timestamps - ruby-on-rails-3

I am using the gem amistad to manage friendships in my application.
I would like to track when relationships occur to be able to display some notifications.
First, I would like to add a timestamp to the relationship model in order to be able to do queries such as : retrieve all Friendships where receiving user is current user, and where updated_at is greater than the last time the current_user checked his notifications. By counting those results I can say: 3 incoming contact requests and display them.
So I made a migration:
class AddUpdatedAtToFriendship < ActiveRecord::Migration
def change
change_table :friendships do |t|
t.timestamps
end
end
end
rake db:migratemigrates correctly, but then the updated_at is not automatically when records are created or updated via the gem (eg: #user.invite another_user).
FYI, the invite method is the following: (code here)
def invite(user)
return false if user == self || find_any_friendship_with(user)
Amistad.friendship_class.new{ |f| f.friendable = self ; f.friend = user }.save
end
I don't see why the active record auto timestamps doesn't work in this case.
Note: If I manually create a friendship in the console, the timestamps are set:
$> rails c
test = Amistad::Friendships::UserFriendship.new
test.friend_id = 1
test.friendable_id = 2
test.save
test.updated_at
=> Thu, 23 May 2013 17:59:17 CEST +02:00
Even If I do that in the console timestamps are set : So it must be a context problem...
$> rails c
test2 = Amistad.friendship_class.new{ |f| f.friendable = User.find_by_id(5) ; f.friend = User.find_by_id(6) }.save
test2.updated_at
=> Thu, 23 May 2013 18:02:05 CEST +02:00
But still, when I call #user.invite another_user in the application, it doesn't update the timestamps...
Second, in the rails console, if I type Friendships.all, Friendship.all, Amistad::Friendships.all... I get :
NameError: uninitialized constant Friendship
How can I solve those 2 problems. Any suggestions ?

To access the Friendship model, use: Amistad::Friendships::UserFriendship.all
The migration is fine. There is nothing else to do in order to have the fields automatically updated.
Don't forget to restart the server so that Active Record picks up the changes of the migration !

Related

Getting previous HABTM values

In my app, I have several clients, and they have several elements (via has_many_through association) depending on a certain BusinessType to which Client belongs to so that instead of manually adding all the elements to the Client, I can just select the BusinessType and everything gets added automatically (business_type in Client is attr_readonly). BusinessType HABTM elements.
Here's the catch, after creation with the default BusinessType, the clients can update their elements and remove or add as they please (mostly add), so what I'm trying to do is the following:
Suppose one business_type has elements [1,2,3] and is assigned to one client, then, the following elements are added manually to the client = [4,5,6] so it ends up having [1,2,3,4,5,6], ok everything's fine here.
But after this, the business_type gets updated and has element 2 removed, so it ends up being [1,3]. Here's the deal, I want the client to be updated by removing the 2, but not the [4,5,6] that do not correspond to the business_type in question so that it ends up [1,3,4,5,6], I'm using an after_update callback to update the clients' elements but the _was method doesn't work for HABTM relationships (to get the old business_type's elements.
I've tried using a before_update callback to first to client.elements = client.elements - business_type.elements to store momentarily in the DB [1,2,3,4,5,6] - [1,2,3] = [4,5,6], and in the after_update do client.elements = client.elements + business_type.elements to get [4,5,6] + [1,3] = [1,3,4,5,6]but this has already the new value of [1,3]. How can I get the old business_type.elements value in the before_update or after_update?
Thanks in advance for your help!
I had a similar problem in an app, and the only solution I could come up with was to store the values before doing update_attributes in the controller.
Example code:
Models
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories, :join_table => "categories_products"
def remember_prev_values(values)
#prev_values = values
end
def before_update_do_something
puts #prev_values - self.category_ids # Any categories removed?
puts self.category_ids - #prev_values # Any categories added?
end
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products, :join_table => "categories_products"
end
In the update method in the products controller I do the following:
class ProductsController < ApplicationController
...
def update
#product.remember_prev_values(#product.category_ids)
if #product.update_attributes(params[:product])
flash[:notice] = "Product was successfully updated."
redirect_to(product_path(#product))
else
render :action => "edit"
end
end
...
end
It is not ideal, but it is then possible to "catch" the habtm inserts/removes before they are executed.
I do think it is possible to do in a callback, but you might need to "hack" into ActiveRecord.
I did not spend much time on trying to dig into ActiveRecord internals, as this is a simple implementation that works.
You should use after_initialize callback to store previous values.
after_initialize do #previous_elements = elements.map{|x| x} end
Note that here we make a copy of assosiations by map function call.

Rails - changed a table name, now tests won't run

Using Rails 3, I've changed the name of a table in the model like this:
# app/models/product.rb
class Product < ActiveRecord::Base
set_table_name "items"
end
But when I try setting up tests, I get the following error:
Started
E
Finished in 0.027396 seconds.
1) Error:
test_the_truth(CustomerTest):
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'project2_test.products' doesn't exist: DELETE FROM `products`
1 tests, 0 assertions, 0 failures, 1 errors
Any idea how I can let it know about Products?
OK found the answer here:
http://www.missiondata.com/blog/systems-integration/80/rails-fixtures-with-models-using-set_table_name/
Had to change the name of the Fixture yml file from Products to Items.
Rather than alter the class directly, you should create a migration. This will allow Rails to smoothly change the database, and allow any others working on the project to change their database in the same manner.
Write a change method which uses rename_table.
class RenameProductsToItems < ActiveRecord::Migration
def change
rename_table :items, :products :string
end
end

How do I lock records in Rails 3 for a specific amount of time?

What I want to do is basically have a user obtain the lock on a record and have it for a specific amount of time so they can make changes to it, like wikipedia. So lets say a wikipedia article gives the user an hour to edit it before other users may edit it.
How could I achieve that with Rails 3? I have read up and found that pessimistic locking is what I should use for the lock. Given that... What kind of mechanism would I use for releasing the lock say after an hour?
My stack is Rails 3, Heroku, PostgreSQL.
Thanks for any answers and I love to see code if you can that would be so awesome!
Here's an example that creates locks, but doesn't delete them.
I leave that up to you.
The locks do expire after an hour in this example, but to complete the app they should automatically be deleted on a successful update of a post.
working example
or read the
relevant commit
You can do this with acts_as_lockable_by gem.
Imagine you have a patient (ActiveRecord) class that can only be edited by one user and it should be locked to this user till he decides to release it:
class Patient < ApplicationRecord
acts_as_lockable_by :id, ttl: 30.seconds
end
Then you can do this in your controller:
class PatientsController < ApplicationController
def edit
if patient.lock(current_user.id)
# It will be locked for 30 seconds for the current user
# You will need to renew the lock by calling /patients/:id/renew_lock
else
# Could not lock the patient record which means it is already locked by another user
end
end
def renew_lock
if patient.renew_lock(current_user.id)
# lock renewed return 200
else
# could not renew the lock, it might be already released
end
end
private
def patient
#patient ||= Patient.find(params[:id])
end
end
Add a field called "editable_until":datetime and set a specific date (Time.now + 30.min f.e.) when creating your record. And simply query this field to find out if the user has the right to update the record or not.
class Post << AR
before_validation :set_editable_time
validate :is_editable
def editable?
self.editable_until.nil? || self.editable_until >= Time.now
end
protected
def is_editable
self.errors[:editable_until] << "cannot be edited anymore" unless editable?
end
def set_editable_time
self.editable_until ||= Time.now + 30.min
end
end
Post.create(:params....)
=> <Post, ID:1, :editable_until => "2011-10-13 15:00:00">
Post.first.editable?
=> true
sleep 1.hour
Post.first.editable?
=> false

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.

Strange behavior of references in MongoDB

I am using Rails 3 with Mongoid.
I have two documents:
class MyUser
include Mongoid::Document
field ......
references_many :statuses, :class_name => "MyStatus"
end
class MyStatus
include Mongoid::Document
field ......
referenced_in :user, :class_name => "MyUser"
end
The problem is, I can get the user of any given status, but I cannot get the list of statuses from a user!
ie.
status = MyStatus.first
status.user # the output is correct here
user = MyUser.first
user.statuses # this one outputs [] instead of the list of statuses...
Please tell me what have I done wrong? I am just a few days with mongo......
Your code looks correct to me.
Are you sure that MyStatus.first.user == MyUser.first ?
It's possible that you have multiple users in your db.. where the first user has no statuses, and the second user has status1 in his list.
To test this, try doing:
status = MyStatus.first
user = status.user
user.statuses # Should return at least one status