How do I create a transaction out of multiple Rails save methods? - ruby-on-rails

I'm using Rails 5. I have a model that looks like this
class CryptoIndexCurrency < ApplicationRecord
belongs_to :crypto_currency
end
I have a service method where I want to populate this table with records, which I do like so
CryptoIndexCurrency.delete_all
currencies.each do |currency|
cindex_currency = CryptoIndexCurrency.new({:crypto_currency => currency})
cindex_currency.save
end
The problem is the above is not very transactional, in as far as if something happens after the first statement, the "delete_all" will have executed but nothing else will have. What is the proper way to create a transaction here and equally as important, where do I place that code? Would like to know the Rails convention here.

I think you can just do:
CryptoIndexCurrency.transaction do
CryptoIndexCurrency.delete_all
CryptoIndexCurrency.create(currencies.map{ |c| {crypto_currency: c} })
end

If you are using Activerecord you can use the builtin transaction mechanism. Otherwise, one way would be to make sure you validate all your data and only save when everything is valid. Take a look at validates_associate and the like.
That said, if your process is inherently non validatable/nondeterministic (eg. you call external APIs to validate a payment) then the best is to ensure you have some cleaning methods that take care of your failure
If you have deterministic failures:
def new_currencies_valid?(currencies)
currencies.each do
return false if not currency.valid?(:create)
end
true
end
if new_currencies_valid?(new_currencies)
Currency.delete_all # See note
new_currencies.each(&:save)
end
A sidenote : unless you really understand what you are doing, I suggest calling destroy_all which runs callbacks on deletion (such as deleting dependent: :destroy) associations

Related

How to skip all callbacks except paper-trail when updating an ActiveRecord model?

I have a model with paper-trail enabled. In one of my API routes, I have to run the_model.update_columns ... so that the model instance can be modified (and saved) without triggering all of the associated callbacks (these callbacks have a ton of side-effects which I don't want for this specific route).
However, I still want this change to be recorded by paper-trail. Is there a reasonable way I can achieve that?
This is simplistic and clunky, but it'd get done exactly what you want:
class ThisModel < ActiveRecord::Base
...
def update_me_no_callbacks(att_1, attr_2, attr_3, ...)
self.update_columns(
attr_1: attr_1,
attr_2: attr_2,
attr_3: attr_3,
...
)
# Do paper-trail code
end
end

Testing an association model helper method rails rspec

I have two models, User and Account.
# account.rb
belongs_to :user
# user.rb
has_one :account
Account has an attribute name. And in my views, I was calling current_user.account.name multiple times, and I heard that's not the great of a way to do it. So I was incredibly swift, and I created the following method in my user.rb
def account_name
self.account.name
end
So now in my view, I can simply call current_user.account_name, and if the association changes, I only update it in one place. BUT my question is, do I test this method? If I do, how do I test it without any mystery guests?
I agree there is nothing wrong with current_user.account.name - while Sandi Metz would tell us "User knows too much about Account" this is kind of the thing you can't really avoid w/ Active Record.
If you found you were doing a lot of these methods all over the User model you could use the rails delegate method:
delegate :name, :to => :account, :prefix => true
using the :prefix => true option will prefix the method in the User model so it is account_name. In this case I would assume you could write a very simple unit test on the method that it returns something just incase the attribute in account would ever change your test would fail so you would know you need to update the delegate method.
There's nothing wrong with current_user.account.name
There's no difference between calling it as current_user.account.name, or making current_user.account_name call it for you
You're probably not calling current_user in the model, like you say
You should have a spec for it if you use it
Personally I see no good reason for any of this. Just use current_user.account.name.
If you are worrying about efficiency, have current_user return a user that joins account.
This is going to be a bit off-topic. So, apologies in advance if it's not interesting or helpful.
TL;DR: Don't put knowledge of your models in your views. Keep your controllers skinny. Here's how I've been doing it.
In my current project, I've been working to make sure my views have absolutely no knowledge of anything about the rest of the system (to reduce coupling). This way, if you decide to change how you implement something (say, current_user.account.name versus current_user.account_name), then you don't have to go into your views and make changes.
Every controller action provides a #results hash that contains everything the view needs to render correctly. The structure of the #results hash is essentially a contract between the view and the controller.
So, in my controller, #results might look something like {current_user: {account: {name: 'foo'}}}. And in my view, I'd do something like #results[:current_user][:account][:name]. I like using a HashWithIndifferentAccess so I could also do #results['current_user']['account']['name'] and not have things blow up or misbehave.
Also, I've been moving as much logic as I can out of controllers into service objects (I call them 'managers'). I find my managers (which are POROs) a lot easier to test than controllers. So, I might have:
# app/controllers/some_controller.rb
class SomeController
def create
#results = SomeManager.create(params)
if #results[:success]
# happy routing
else
# sad routing
end
end
end
Now, my controllers are super skinny and contain no logic other than routing. They don't know anything about my models. (In fact, almost all of my controller actions look exactly the same with essentially the same six lines of code.) Again, I like this because it creates separation.
Naturally, I need the manager:
#app/managers/some_manager.rb
class SomeManager
class << self
def create(params)
# do stuff that ends up creating the #results hash
# if things went well, the return will include success: true
# if things did not go well, the return will not include a :success key
end
end
end
So, in truth, the structure of #results is a contract between the view and the manager, not between the view and the controller.

FactoryGirl build_stubbed strategy with a has_many association

Given a standard has_many relationship between two objects. For a simple example, let's go with:
class Order < ActiveRecord::Base
has_many :line_items
end
class LineItem < ActiveRecord::Base
belongs_to :order
end
What I'd like to do is generate a stubbed order with a list of stubbed line items.
FactoryGirl.define do
factory :line_item do
name 'An Item'
quantity 1
end
end
FactoryGirl.define do
factory :order do
ignore do
line_items_count 1
end
after(:stub) do |order, evaluator|
order.line_items = build_stubbed_list(:line_item, evaluator.line_items_count, :order => order)
end
end
end
The above code does not work because Rails wants to call save on the order when line_items is assigned and FactoryGirl raises an exception:
RuntimeError: stubbed models are not allowed to access the database
So how do you (or is it possible) to generate an stubbed object where it's has_may collection is also stubbed?
TL;DR
FactoryGirl tries to be helpful by making a very large assumption when it
creates it's "stub" objects. Namely, that:
you have an id, which means you are not a new record, and thus are already persisted!
Unfortunately, ActiveRecord uses this to decide if it should
keep persistence up to date.
So the stubbed model attempts to persist the records to the database.
Please do not try to shim RSpec stubs / mocks into FactoryGirl factories.
Doing so mixes two different stubbing philosophies on the same object. Pick
one or the other.
RSpec mocks are only supposed to be used during certain parts of the spec
life cycle. Moving them into the factory sets up an environment which will
hide the violation of the design. Errors which result from this will be
confusing and difficult to track down.
If you look at the documentation for including RSpec into say
test/unit,
you can see that it provides methods for ensuring that the mocks are properly
setup and torn down between the tests. Putting the mocks into the factories
provides no such guarantee that this will take place.
There are several options here:
Don't use FactoryGirl for creating your stubs; use a stubbing library
(rspec-mocks, minitest/mocks, mocha, flexmock, rr, or etc)
If you want to keep your model attribute logic in FactoryGirl that's fine.
Use it for that purpose and create the stub elsewhere:
stub_data = attributes_for(:order)
stub_data[:line_items] = Array.new(5){
double(LineItem, attributes_for(:line_item))
}
order_stub = double(Order, stub_data)
Yes, you do have to manually create the associations. This is not a bad thing,
see below for further discussion.
Clear the id field
after(:stub) do |order, evaluator|
order.id = nil
order.line_items = build_stubbed_list(
:line_item,
evaluator.line_items_count,
order: order
)
end
Create your own definition of new_record?
factory :order do
ignore do
line_items_count 1
new_record true
end
after(:stub) do |order, evaluator|
order.define_singleton_method(:new_record?) do
evaluator.new_record
end
order.line_items = build_stubbed_list(
:line_item,
evaluator.line_items_count,
order: order
)
end
end
What's Going On Here?
IMO, it's generally not a good idea to attempt to create a "stubbed" has_many
association with FactoryGirl. This tends to lead to more tightly coupled code
and potentially many nested objects being needlessly created.
To understand this position, and what is going on with FactoryGirl, we need to
take a look at a few things:
The database persistence layer / gem (i.e. ActiveRecord, Mongoid,
DataMapper, ROM, etc)
Any stubbing / mocking libraries (mintest/mocks, rspec, mocha, etc)
The purpose mocks / stubs serve
The Database Persistence Layer
Each database persistence layer behaves differently. In fact, many behave
differently between major versions. FactoryGirl tries to not make assumptions
about how that layer is setup. This gives them the most flexibility over the
long haul.
Assumption: I'm guessing you are using ActiveRecord for the remainder of
this discussion.
As of my writing this, the current GA version of ActiveRecord is 4.1.0. When
you setup a has_many association on it,
there's
a
lot
that
goes
on.
This is also slightly different in older AR versions. It's very different in
Mongoid, etc. It's not reasonable to expect FactoryGirl to understand the
intricacies of all of these gems, nor differences between versions. It just so
happens that the has_many association's writer
attempts to keep persistence up to date.
You may be thinking: "but I can set the inverse with a stub"
FactoryGirl.define do
factory :line_item do
association :order, factory: :order, strategy: :stub
end
end
li = build_stubbed(:line_item)
Yep, that's true. Though it's simply because AR decided not to
persist.
It turns out this behavior is a good thing. Otherwise, it would be very
difficult to setup temp objects without hitting the database frequently.
Additionally, it allows for multiple objects to be saved in a single
transaction, rolling back the whole transaction if there was a problem.
Now, you may be thinking: "I totally can add objects to a has_many without
hitting the database"
order = Order.new
li = order.line_items.build(name: 'test')
puts LineItem.count # => 0
puts Order.count # => 0
puts order.line_items.size # => 1
li = LineItem.new(name: 'bar')
order.line_items << li
puts LineItem.count # => 0
puts Order.count # => 0
puts order.line_items.size # => 2
li = LineItem.new(name: 'foo')
order.line_items.concat(li)
puts LineItem.count # => 0
puts Order.count # => 0
puts order.line_items.size # => 3
order = Order.new
order.line_items = Array.new(5){ |n| LineItem.new(name: "test#{n}") }
puts LineItem.count # => 0
puts Order.count # => 0
puts order.line_items.size # => 5
Yep, but here order.line_items is really an
ActiveRecord::Associations::CollectionProxy.
It defines it's own build,
#<<,
and #concat
methods. Of, course these really all delegate back to the association defined,
which for has_many are the equivalent methods:
ActiveRecord::Associations::CollectionAssocation#build
and ActiveRecord::Associations::CollectionAssocation#concat.
These take into account the current state of the base model instance in order
to decide whether to persist now or later.
All FactoryGirl can really do here is let the behavior of the underlying class
define what should happen. In fact, this lets you use FactoryGirl to
generate any class, not
just database models.
FactoryGirl does attempt to help a little with saving objects. This is mostly
on the create side of the factories. Per their wiki page on
interaction with ActiveRecord:
...[a factory] saves associations first so that foreign keys will be properly
set on dependent models. To create an instance, it calls new without any
arguments, assigns each attribute (including associations), and then calls
save!. factory_girl doesn’t do anything special to create ActiveRecord
instances. It doesn’t interact with the database or extend ActiveRecord or
your models in any way.
Wait! You may have noticed, in the example above I slipped the following:
order = Order.new
order.line_items = Array.new(5){ |n| LineItem.new(name: "test#{n}") }
puts LineItem.count # => 0
puts Order.count # => 0
puts order.line_items.size # => 5
Yep, that's right. We can set order.line_items= to an array and it isn't
persisted! So what gives?
The Stubbing / Mocking Libraries
There are many different types and FactoryGirl works with them all. Why?
Because FactoryGirl doesn't do anything with any of them. It's completely
unaware of which library you have.
Remember, you add the FactoryGirl syntax to your test library of choice.
You don't add your library to FactoryGirl.
So if FactoryGirl isn't using your preferred library, what is it doing?
The Purpose Mocks / Stubs Serve
Before we get to the under the hood details, we need to define what
a
"stub"
is
and its intended purpose:
Stubs provide canned answers to calls made during the test, usually not
responding at all to anything outside what's programmed in for the test.
Stubs may also record information about calls, such as an email gateway stub
that remembers the messages it 'sent', or maybe only how many messages it
'sent'.
this is subtly different from a "mock":
Mocks...: objects pre-programmed with expectations which form a
specification of the calls they are expected to receive.
Stubs serve as a way to setup collaborators with canned responses. Sticking to
only the collaborators public API which you touch for the specific test keeps
stubs lightweight and small.
Without any "stubbing" library, you can easily create your own stubs:
stubbed_object = Object.new
stubbed_object.define_singleton_method(:name) { 'Stubbly' }
stubbed_object.define_singleton_method(:quantity) { 123 }
stubbed_object.name # => 'Stubbly'
stubbed_object.quantity # => 123
Since FactoryGirl is completely library agnostic when it comes to their
"stubs", this is the approach they take.
Looking at the FactoryGirl v.4.4.0 implementation, we can see that the
following methods are all stubbed when you build_stubbed:
persisted?
new_record?
save
destroy
connection
reload
update_attribute
update_column
created_at
These are all very ActiveRecord-y. However, as you have seen with has_many,
it is a fairly leaky abstraction. The ActiveRecord public API surface area is
very large. It's not exactly reasonable to expect a library to fully cover it.
Why does the has_many association not work with the FactoryGirl stub?
As noted above, ActiveRecord checks it's state to decide if it should
keep persistence up to date.
Due to the stubbed definition of new_record?
setting any has_many will trigger a database action.
def new_record?
id.nil?
end
Before I throw out some fixes, I want to go back to the definition of a stub:
Stubs provide canned answers to calls made during the test, usually not
responding at all to anything outside what's programmed in for the test.
Stubs may also record information about calls, such as an email gateway stub
that remembers the messages it 'sent', or maybe only how many messages it
'sent'.
The FactoryGirl implementation of a stub violates this tenet. Since it has no
idea what you are going to be doing in your test/spec, it simply tries to
prevent database access.
Fix #1: Do Not Use FactoryGirl to Create Stubs
If you wish to create / use stubs, use a library dedicated to that task. Since
it seems you are already using RSpec, use it's double feature (and the new verifying
instance_double,
class_double,
as well as object_double
in RSpec 3). Or
use Mocha, Flexmock, RR, or anything else.
You can even roll your own super simple stub factory (yes there are issues with
this, it's simply an example of an easy way to make an object with canned
responses):
require 'ostruct'
def create_stub(stubbed_attributes)
OpenStruct.new(stubbed_attributes)
end
FactoryGirl makes it very easy to create 100 model objects when really you
needed 1. Sure, this is a responsible usage issue; as always great power comes
create responsibility. It's just very easy to overlook deeply nested
associations, which don't really belong in a stub.
Additionally, as you have noticed, FactoryGirl's "stub" abstraction is a bit
leaky forcing you to understand both its implementation and your database
persistence layer's internals. Using a stubbing lib should completely free you
from having this dependency.
If you want to keep your model attribute logic in FactoryGirl that's fine.
Use it for that purpose and create the stub elsewhere:
stub_data = attributes_for(:order)
stub_data[:line_items] = Array.new(5){
double(LineItem, attributes_for(:line_item))
}
order_stub = double(Order, stub_data)
Yes, you do have to manually setup the associations. Though you only setup
those associations which you need for the test/spec. You don't get the 5 other
ones that you do not need.
This is one thing that having a real stubbing lib helps make explicitly clear.
This is your tests/specs giving you feedback on your design choices. With a
setup like this, a reader of the spec can ask the question: "Why do we need 5
line items?" If it's important to the spec, great it's right there up front
and obvious. Otherwise, it shouldn't be there.
The same thing goes for those a long chain of methods called a single object,
or a chain of methods on subsequent objects, it's probably time to stop. The
law of demeter is there to help
you, not hinder you.
Fix #2: Clear the id field
This is more of a hack. We know that the default stub sets an id. Thus, we
simply remove it.
after(:stub) do |order, evaluator|
order.id = nil
order.line_items = build_stubbed_list(
:line_item,
evaluator.line_items_count,
order: order
)
end
We can never have a stub which returns an id AND sets up a has_many
association. The definition of new_record? that FactoryGirl setup completely
prevents this.
Fix #3: Create your own definition of new_record?
Here, we separate the concept of an id from where the stub is a
new_record?. We push this into a module so we can re-use it in other places.
module SettableNewRecord
def new_record?
#new_record
end
def new_record=(state)
#new_record = !!state
end
end
factory :order do
ignore do
line_items_count 1
new_record true
end
after(:stub) do |order, evaluator|
order.singleton_class.prepend(SettableNewRecord)
order.new_record = evaluator.new_record
order.line_items = build_stubbed_list(
:line_item,
evaluator.line_items_count,
order: order
)
end
end
We still have to manually add it for each model.
I've seen this answer floating around, but ran into the same problem you had:
FactoryGirl: Populate a has many relation preserving build strategy
The cleanest way that I've found is to explicitly stub out the association calls as well.
require 'rspec/mocks/standalone'
FactoryGirl.define do
factory :order do
ignore do
line_items_count 1
end
after(:stub) do |order, evaluator|
order.stub(line_items).and_return(FactoryGirl.build_stubbed_list(:line_item, evaluator.line_items_count, :order => order))
end
end
end
Hope that helps!
I found the solution of Bryce to be the most elegant but it produces a deprecation warning about the new allow() syntax.
In order to use the new (cleaner) syntax I did this :
UPDATE 06/05/2014 : my first proposition was using a private api method, thanks to Aaraon K for a much nicer solution, please read the comment for further discussion
#spec/support/initializers/factory_girl.rb
...
#this line enables us to use allow() method in factories
FactoryGirl::SyntaxRunner.include(RSpec::Mocks::ExampleMethods)
...
#spec/factories/order_factory.rb
...
FactoryGirl.define do
factory :order do
ignore do
line_items_count 1
end
after(:stub) do |order, evaluator|
items = FactoryGirl.build_stubbed_list(:line_item, evaluator.line_items_count, :order => order)
allow(order).to receive(:line_items).and_return(items)
end
end
end
...

Is there a way to prevent serialized attributes in rails from getting updated even if there are not changes?

This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.
But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.
Suggestions?
Here is a similar solution for Rails 3.1.3.
From: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data
Put the following code in config/initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Now partial updates work great for me, and the field is only updated when the data is actually changed.
The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.
You may start with a chain like this
update -> update_with_foo -> update_with_bar -> update_with_baz
Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz
Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:
alias_method_chain :update, :bar2
Unfortunately, this will get you the following chain:
update -> update_with_bar2 -> update_with_baz
So update_with_foo is gone!
So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.
Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.
There is also discussions in https://github.com/rails/rails/issues/8328.

I want to map my database lookup tables to a hash, good idea?

I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:
table name: donations
id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at
The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end
To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to.
But is there a better way to do what I am trying to do? Should I not worry about queries like this?
Thanks!
Since you have two models, you should use ActiveRecord Model Associations when building the models.
class Donation
has_one :donation_status
end
class DonationStatus
belongs_to :donation
end
Then when you do
my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
#do something
end
For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done
How about putting it in a constant? For example, something like this:
class DonationStatus < ActiveRecord::Base
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
PENDING_DONATION_ID = DonationStatus.find_by_name("pending").id
# ...
end
class DonationsController < ApplicationController
def some_action
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
#do something
end
end
This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.
BTW, I learned this trick in Dan Chak's book, Enterprise Rails.
EDIT: I forgot to mention: in practice, I declare constants like this:
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
What you could do is add this method to Donation:
# Donation.rb
def completed?
self.donation_status.name == 'completed' ? true : false
end
And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.
You could add memcached if you want, or use Rails' caching further, and do:
def completed?
return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
self.donation_status.name == 'completed' ? true : false
end
end
What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.

Resources