I have a review section which allows a user to write a review of another user following their meetup. I want to permit users to write a review following the start_time of their meetup.
The problem I am having is, immediately upon a meetup between two users being created the users are allowed to write a review for one another before their meetup start_time. The method I created finished_meetup? is reading true all the time. I think this is due to my start_time being displayed wrong.
In the console, if I book a meetup for 8:30pm I get the following as the start_time start_time: "2000-01-01 20:30:00". Date is attached to time and cannot be eliminated because "Represent a time with no date in ruby".
How would I set up the method finished_meetup? to allow for the reviews to be done following the meetup and not beforehand.
shema.rb:
create_table "user_meetups", force: true do |t|
t.integer "user_id"
t.integer "friend_id"
t.string "state"
t.datetime "created_at"
t.datetime "updated_at"
t.date "start_date"
t.time "start_time"
user.rb
def find_corresponding_friend_id(friend_id)
self.user_meetups.where(friend_id:friend_id).present?
end
def already_reviewed
self.reviews.map{|d| d.review_writer_id}
end
def finished_meetup?
user_meetups.where("start_time < ?", Time.new("2000/#{Time.now.strftime("%m/%d")}"))
end
users/show.html.erb
<% if #user.find_corresponding_friend_id(current_user.id) && #user.already_reviewed.empty? && #user.finished_meetup? %>
user_meetup.rb
class UserMeetup < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'
validates :start_date, :start_time, presence: true
# attr_accessor :user, :friend, :user_id, :friend_id, :state
after_destroy :delete_mutual_meetup!
state_machine :state, initial: :pending do
after_transition on: :accept, do: [:accept_mutual_meetup!]
after_transition on: :block, do: [:block_mutual_meetup!]
after_transition on: :unblock, do: [:accept_mutual_meetup!]
state :requested
state :blocked
event :accept do
transition any => :accepted
end
event :block do
transition any => :blocked
end
event :unblock do
transition any => :accepted
end
end
def self.request(start_date, start_time, location, description, learners, user1, user2)
transaction do
# Rails.logger.info "user1 is #{user1.inspect}"
# Rails.logger.info "user2 is #{user2.inspect}"
meetup1 = UserMeetup.create!(start_date: start_date, start_time: start_time, user: user1, friend: user2, state: 'pending')
# Rails.logger.info "meetup1 is #{meetup1.inspect}"
meetup2 = UserMeetup.create!(start_date: start_date, start_time: start_time, user: user2, friend: user1, state: 'requested' )
# meetup1.send_request_email
# meetup1
end
end
this method will return the list of user meetups started before the current time
def finished_meetup
self.user_meetups.where("start_time < ?", Time.now.to_s.gsub(/#{Date.today.to_s}/, '2000-01-01'))
end
Your date/time separation is complicating things. Change your start_time and start_date to a start_at datetime column. The migration will look something like this:
class SomeMigrationClass < ActiveRecord:Migration
def change
add_column :user_meetups, :start_at, :datetime
UserMeetups.find_in_batches.each do |um|
d = um.start_date
t = um.start_time
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec, t.zone)
um.update!(start_at: dt)
end
remove_columns :user_meetups, :start_time, :start_date
end
end
This should preserve any existing data you have and combine them into datetimes. Untested of course, if you have production data test test test!
Next, just compare that datetime with the current date.
def finished_meetups
self.user_meetups.where('start_at < ?', Time.current)
end
This will return the user's meetups that have start_at times occurring before the current time.
If you really want a boolean response on whether there are any finished meetups, you could build on this with:
def finished_meetup?
self.finished_meetups.any?
end
What you have done is not a true or false case. It will either return an array or and empty array. Neither of which are ever false.
Try using "empty?"
def finished_meetup?
!self.finished_meetups.empty?
end
This is essentially asking if finished_meetups is NOT empty.
-----UPDATE-----
Well, seems that the problem was in last.id. When database is created works OK, but when not fails. Now the question is different: How can I create a field using the id from the same row?
--------ORIGINAL------
I'm working with active record in pure ruby (without Rails), and I'm literally getting crazy with this.
This is my code
class Enviroment < ActiveRecord::Base
#self.table_name = 'enviroments'
self.connection.create_table(:enviroments, :force=>true) do |t|
t.column :name, :string, :default=>'env-'+ (last.id-1).to_s
t.column :ssh, :string, :default=>nil
end
end
and here the error:
ActiveRecord::StatementInvalid: Could not find table 'enviroments'
from /usr/lib/ruby/gems/1.8/gems/activerecord-3.2.3/lib/active_record/connection_adapters/sqlite_adapter.rb:465:in `table_structure'
if I useself.table_name = 'enviroments' still not working. I've updated the gems and neither.
I'm newbie with ruby and databases, but I can't understand this problem, I think this same code worked in the past :S
Your code to create the table (very odd to have that in the model by the way) is calling last.id, and of course to call last the table must already exist.
Because you're passing :force => true to create_table you'll actually destroy the table if it already exists.
You could probably make your code work if you stashed the value of last.id in a local variable before the call to create_table but I don't understand why you are creating tables like this.
Finally, this was my solution:
class Enviroment < ActiveRecord::Base
after_create :create_default
private
def create_default
if name == nil
s = 'env-' + self.id.to_s
self.name = s
self.save
end
end
end
class CreateSchema < ActiveRecord::Migration
create_table(:enviroments, :force=>true) do |t|
t.column :name, :string, :default=>nil
t.column :ssh, :string, :default=>nil
end
I'm using Ruby and PostgreSQL and have created 3 distinct DB schemas: billing (for billing related data), customer (for customer related data) and edocs (for electronic documents related data).
I'm not using Rails so I have a stand-alone migration code like this:
#migrate.rb
if ARGV[0] =~ /VERSION=\d+/
version = ARGV[0].split('=')[1].to_i
else
version = nil
end
ActiveRecord::Base.default_timezone = :utc
#logger = Logger.new $stderr
ActiveRecord::Base.logger = #logger
ActiveSupport::LogSubscriber.colorize_logging = false
#config = YAML.load_file(File.join(File.dirname(__FILE__), 'database.yml'))
ActiveRecord::Base.establish_connection(#config["edocs"])
ActiveRecord::Migrator.migrate(".", version)
I have already realized that I probably have to create a different directories to contain the migration for the different schemas, and changing connection info for each migrate.rb.
But I'm not sure how I'm going to make a table reference another table that is in another schema.
For example:
class CreateBillingEventsTable < ActiveRecord::Migration
def self.up
create_table :billing_events do |t|
t.references :customer, :null => false
t.references :service_type, :null => false
t.timestamps
end
change_table :billing_events do |t|
t.index [:customer_id, :created_at]
end
end
def self.down
remove_index :billing_events, :column => [:customer_id, :created_at]
drop_table :billing_events
end
end
In the above example, "customer" and "billing_events" are in different schemas.
How can I code that?
Thanks.
I've looked into some tutes and all I saw were old posts on how to test before_create. Also it seems like they're all just testing that before_create was called i.e.:
#user = User.new
#user.should_receive(:method_name_called_by_before_create)
#user.send(:before_create) (sometimes they just do #user.save)
I want to actually test that my method worked and that it had assigned(and saved the variables) after creating the record.
Here are my models:
user.rb
class User < ActiveRecord::Base
has_one :character, :dependent => :destroy
after_create :generate_character
private
def generate_character
self.create_character(:name => "#{email}'s avatar")
end
end
and character.rb
class Character < ActiveRecord::Base
belongs_to :user
before_create :generate_character
private
def generate_character
response = api_call
#API CALL HERE
#set object attributes here
self.stat1 = calculate_stat1(response) + 5
self.stat2 = calculate_stat2(response) + 5
self.stat3 = calculate_stat3(response) + 5
end
def api_call
return api_call_response
end
end
I want to test that generate character indeed set the attributes without going online and calling the API call. Is this possible with rspec? I have a fixture of a json response so I was hoping I can stub out generate character and then use the fake response for testing.
Here's my character.spec:
describe Character do
before(:each) do
Character.any_instance.stub!(:api_call).and_return(fake_response.read)
#user = Factory(:user)
#character = #user.character
puts #character.inspect
end
def fake_response
File.open("spec/fixtures/api_response.json")
end
It prints out only 5 for each of the character's stats. Also I did a puts response in the generate_character method in character.rb and it still prints out the "real" api call.
I managed to do a puts in fake_response and it does goes through there but it also goes through the "real" api_call after, which makes the stub obsolete. How do I get through this?
A good approach here is extracting your api call into a self contained method. Something like this:
class Character < ActiveRecord::Base
belongs_to :user
before_create :generate_character
private
def generate_character
data = api_call
#set object attributes from data
end
def api_call
# returns a data structure
# resulting from the call
end
end
Then use RSpec's any_instance to stub the api_call method to return a fixed data structure
Character.any_instance.stub!(:api_call).and_return { {:id => 1, :attribute_one => "foo"} }
#user = User.create
#user.character.attribute_one.should == "foo"
for more info on any_instance check this commit
I have a huge project with both of ActiveRecord and ActiveResource models. I need to implement logging of user activity with these models and also to log changes of model attributes (save object state or somthing like that). Changes can made by users or cron rake tasks.
I also must have possibility to search any data by date , any field ..etc
Will be nice also to generate readable messages with last activity , for example
User Bob change his password to * and email to ** at 2011-08-12 08:12
Staff Jeff added new partner: Company name at 2011-08-12 08:13
Admin Jack deleted product : Product name at 2011-09-12 11:11
Client Sam ordered new service : Service name at 2011-09-12 11:12
Does anybody implement such logging? Ideas? Advices?
should I use gems or can I do all the logic with observers not changing models?
I liked gem https://github.com/airblade/paper_trail can anybody say how can I make it work with activeresource ?
You are looking for
https://github.com/collectiveidea/acts_as_audited
Few open source projects use that plugin I think Red Mine as well as The Foreman.
Edit: Unfortunately it can do only ActiveRecord, not ActiveResource.
Fivell, I just saw this question and don't have time to work up alterations this evening before the bounty expires, so I'll give you my auditing code that works with ActiveRecord and should work with ActiveResource, perhaps with a few tweaks (I don't use ARes often enough to know offhand). I know the callbacks we use are there, but I'm not sure if ARes has ActiveRecord's dirty attribute changes tracking.
This code logs each CREATE/UPDATE/DELETE on all models (excepting CREATEs on the audit log model and any other exceptions you specify) with the changes stored as JSON. A cleaned backtrace is also stored so you can determine what code made the change (this captures any point in your MVC as well as rake tasks and console usage).
This code works for console usage, rake tasks, and http requests, although generally only the last one logs the current user. (If I recall correctly, the ActiveRecord observer that this replaced did not work in rake tasks or the console.) Oh, this code comes from a Rails 2.3 app - I have a couple Rails 3 apps, but I haven't needed this kind of auditing for them yet.
I don't have code that builds a nice display of this information (we only dig into the data when we need to look into an issue), but since the changes are stored as JSON it should be fairly straightforward.
First, we store the current user in User.current so it is accessible everywhere, so in app/models/user.rb:
Class User < ActiveRecord::Base
cattr_accessor :current
...
end
The current user is set in the application controller for each request like so (and does not cause concurrency issues):
def current_user
User.current = session[:user_id] ? User.find_by_id(session[:user_id]) : nil
end
You could set User.current in your rake tasks if it made sense to.
Next, we define the model to store the audit info app/models/audit_log_entry.rb - you'll want to customize IgnoreClassesRegEx to fit any models you don't want audited:
# == Schema Information
#
# Table name: audit_log_entries
#
# id :integer not null, primary key
# class_name :string(255)
# entity_id :integer
# user_id :integer
# action :string(255)
# data :text
# call_chain :text
# created_at :datetime
# updated_at :datetime
#
class AuditLogEntry < ActiveRecord::Base
IgnoreClassesRegEx = /^ActiveRecord::Acts::Versioned|ActiveRecord.*::Session|Session|Sequence|SchemaMigration|CronRun|CronRunMessage|FontMetric$/
belongs_to :user
def entity (reload = false)
#entity = nil if reload
begin
#entity ||= Kernel.const_get(class_name).find_by_id(entity_id)
rescue
nil
end
end
def call_chain
return if call_chain_before_type_cast.blank?
if call_chain_before_type_cast.instance_of?(Array)
call_chain_before_type_cast
else
JSON.parse(call_chain_before_type_cast)
end
end
def data
return if data_before_type_cast.blank?
if data_before_type_cast.instance_of?(Hash)
data_before_type_cast
else
JSON.parse(data_before_type_cast)
end
end
def self.debug_entity(class_name, entity_id)
require 'fastercsv'
FasterCSV.generate do |csv|
csv << %w[class_name entity_id date action first_name last_name data]
find_all_by_class_name_and_entity_id(class_name, entity_id,
:order => 'created_at').each do |a|
csv << [a.class_name, a.entity_id, a.created_at, a.action,
(a.user && a.user.first_name), (a.user && a.user.last_name), a.data]
end
end
end
end
Next we add some methods to ActiveRecord::Base to make the audits work. You'll want to look at the audit_log_clean_backtrace method and modify for your needs. (FWIW, we put additions to existing classes in lib/extensions/*.rb which are loaded in an initializer.) In lib/extensions/active_record.rb:
class ActiveRecord::Base
cattr_accessor :audit_log_backtrace_cleaner
after_create :audit_log_on_create
before_update :save_audit_log_update_diff
after_update :audit_log_on_update
after_destroy :audit_log_on_destroy
def audit_log_on_create
return if self.class.name =~ /AuditLogEntry/
return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
audit_log_create 'CREATE', self, caller
end
def save_audit_log_update_diff
#audit_log_update_diff = changes.reject{ |k,v| 'updated_at' == k }
end
def audit_log_on_update
return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
return if #audit_log_update_diff.empty?
audit_log_create 'UPDATE', #audit_log_update_diff, caller
end
def audit_log_on_destroy
return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
audit_log_create 'DESTROY', self, caller
end
def audit_log_create (action, data, call_chain)
AuditLogEntry.create :user => User.current,
:action => action,
:class_name => self.class.name,
:entity_id => id,
:data => data.to_json,
:call_chain => audit_log_clean_backtrace(call_chain).to_json
end
def audit_log_clean_backtrace (backtrace)
if !ActiveRecord::Base.audit_log_backtrace_cleaner
ActiveRecord::Base.audit_log_backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/rake\.rb/ }
ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/bin\/rake/ }
ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/(action_controller|active_(support|record)|hoptoad_notifier|phusion_passenger|rack|ruby|sass)\// }
ActiveRecord::Base.audit_log_backtrace_cleaner.add_filter { |line| line.gsub(RAILS_ROOT, '') }
end
ActiveRecord::Base.audit_log_backtrace_cleaner.clean backtrace
end
end
Finally, here are the tests we have on this - you'll need to modify the actual test actions of course. test/integration/audit_log_test.rb
require File.dirname(__FILE__) + '/../test_helper'
class AuditLogTest < ActionController::IntegrationTest
def setup
end
def test_audit_log
u = users(:manager)
log_in u
a = Alert.first :order => 'id DESC'
visit 'alerts/new'
fill_in 'alert_note'
click_button 'Send Alert'
a = Alert.first :order => 'id DESC', :conditions => ['id > ?', a ? a.id : 0]
ale = AuditLogEntry.first :conditions => {:class_name => 'Alert', :entity_id => a.id }
assert_equal 'Alert', ale.class_name
assert_equal 'CREATE', ale.action
end
private
def log_in (user, password = 'test', initial_url = home_path)
visit initial_url
assert_contain 'I forgot my password'
fill_in 'email', :with => user.email
fill_in 'password', :with => password
click_button 'Log In'
end
def log_out
visit logout_path
assert_contain 'I forgot my password'
end
end
And test/unit/audit_log_entry_test.rb:
# == Schema Information
#
# Table name: audit_log_entries
#
# id :integer not null, primary key
# class_name :string(255)
# action :string(255)
# data :text
# user_id :integer
# created_at :datetime
# updated_at :datetime
# entity_id :integer
# call_chain :text
#
require File.dirname(__FILE__) + '/../test_helper'
class AuditLogEntryTest < ActiveSupport::TestCase
test 'should handle create update and delete' do
record = Alert.new :note => 'Test Alert'
assert_difference 'Alert.count' do
assert_difference 'AuditLogEntry.count' do
record.save
ale = AuditLogEntry.first :order => 'created_at DESC'
assert ale
assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
end
end
assert_difference 'AuditLogEntry.count' do
record.update_attribute 'note', 'Test Update'
ale = AuditLogEntry.first :order => 'created_at DESC'
expected_data = {'note' => ['Test Alert', 'Test Update']}
assert ale
assert_equal 'UPDATE', ale.action, 'AuditLogEntry.action should be UPDATE'
assert_equal expected_data, ale.data
assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
end
assert_difference 'AuditLogEntry.count' do
record.destroy
ale = AuditLogEntry.first :order => 'created_at DESC'
assert ale
assert_equal 'DESTROY', ale.action, 'AuditLogEntry.action should be CREATE'
assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
assert_nil Alert.find_by_id(record.id), 'Alert should be deleted'
end
end
test 'should not log AuditLogEntry create entry and block on update and delete' do
record = Alert.new :note => 'Test Alert'
assert_difference 'Alert.count' do
assert_difference 'AuditLogEntry.count' do
record.save
end
end
ale = AuditLogEntry.first :order => 'created_at DESC'
assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
assert_nil AuditLogEntry.first(:conditions => { :class_name => 'AuditLogEntry', :entity_id => ale.id })
if ale.user_id.nil?
u = User.first
else
u = User.first :conditions => ['id != ?', ale.user_id]
end
ale.user_id = u.id
assert !ale.save
assert !ale.destroy
end
end
https://github.com/collectiveidea/acts_as_audited
and
https://github.com/airblade/paper_trail
are both great solutions for ActiveRecord only, but since much of ActiveRecord has been extracted to ActiveModel, it's likely to be reasonable to extend either to support ActiveResource as well, at least for read-only support. I looked through the Github network graphs and googled around and there doesn't appear to be any ongoing development of such a solution, nevertheless I expect it will be easier to implement on top of one of these two plugins than starting from scratch. paper_trail appears to be under more active development and has some commits for Rails 3.1, so it may be more up to date with Rails internals and easier to extend, but that's just a gut instinct—I'm not familiar with the internals of either.
The acts_as_audited gem should work well for you:
https://github.com/collectiveidea/acts_as_audited
And as far as ActiveResource is considered, it will also be a model in some other application. You can use the gem at the server side, and you don't need to audit it at the client side. All the CRUD operations using ActiveResource would finally translate to CRUD operations on the ActiveRecord (on server side).
So probably you need to look at it from a distance, and the same solution would apply in both the cases, but at different places.
for tracking user activity(CRUD ), i've created a class inherits from Logger, and now I am planing to write a litle plugin for tracking user that i can use for any ROR application built. I have already checked if there is a plugin like that but I didn’t see. I guess there are many gem like paper-trail, acts_as_audited or itslog but i prefer to use a plugin. Any suggestions?
Here is a link that might help you : http://robaldred.co.uk/2009/01/custom-log-files-for-your-ruby-on-rails-applications/comment-page-1/#comment-342
nice coding