Rails Active Record: Calling Build Method Should Not Save To Database Before Calling Save Method - ruby

I have a simple user model
class User < ActiveRecord::Base
has_one :user_profile
end
And a simple user_profile model
class UserProfile < ActiveRecord::Base
belongs_to :user
end
The issue is when I call the following build method, without calling the save method, I end up with a new record in the database (if it passes validation)
class UserProfilesController < ApplicationController
def create
#current_user = login_from_session
#user_profile = current_user.build_user_profile(params[:user_profile])
##user_profile.save (even with this line commented out, it still save a new db record)
redirect_to new_user_profile_path
end
Anyyyyyy one have anyyy idea what's going on.
The definition of this method says the following but it's still saving for me
build_association(attributes = {})
Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one

Ok, I'm sure experienced vets already know this, but as a rookie I had to figure it out the long way...let me see if I can explain this without screwing it up
Although I was not directly saving the user_profile object, I noticed in my logs that something was updating the user model's last_activity_time (and the user_profile model) each time I submitted the form (the user model's last_activity date was also updated when the logged in user did various other things too - I later realized this was set in the Sorcery gem configuration).
Per http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
AutosaveAssociation is a module that takes care of automatically saving associated records when their parent is saved. In my case, the user mode is the parent and the scenario they provide below mirrors my experience.
class Post
has_one :author, :autosave => true
end
post = Post.find(1)
post.title # => "The current global position of migrating ducks"
post.author.name # => "alloy"
post.title = "On the migration of ducks"
post.author.name = "Eloy Duran"
post.save
post.reload
post.title # => "On the migration of ducks"
post.author.name # => "Eloy Duran"
The following resolutions resolved my problem
1. Stopping Sorcery (config setting) from updating the users last_activity_time (for every action)
or
2. Passing an ':autosave => false' option when I set the association in the user model as follows
class User < ActiveRecord::Base
has_one :user_profile, :autosave => false
end

Related

Caching active record associations with fresh_when

What's the best way to cache ActiveRecord associations? Here's what I am trying to do in the controller:
def all_posts
#posts = User.find(params[:id]).posts
fresh_when #posts
end
Whenever a new post is added to the user model I need to clear the cache.
You're solution works perfect.
In case you are just adding posts there is a more performant solution: You'll have to had a touch: true to the belongs_to :user part of your Post model. ActiveRecord will touch the User object every time a post gets changed and a new post gets created. When you have that implemented you can use this controller code:
def all_posts
user = User.find(params[:id])
#posts = user.posts
fresh_when user
end
It is much faster to generate a cache key from one user than from many posts.
Have a look at http://www.xyzpub.com/en/ruby-on-rails/3.2/caching.html to get an introduction to caching in Rails.

Rails nested form on many-to-many: how to prevent duplicates?

I've setup a nested form in my rails 3.2.3 app, it's working fine, my models are:
class Recipe < ActiveRecord::Base
attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes
has_and_belongs_to_many :ingredient_lines
accepts_nested_attributes_for :ingredient_lines
end
and:
class IngredientLine < ActiveRecord::Base
attr_accessible :ingredient_id, :measurement_unit_id, :quantity
has_and_belongs_to_many :recipes
belongs_to :measurement_unit
belongs_to :ingredient
end
As above, a Recipe can have multiple IngredientLines and vice versa.
What I'm trying to avoid is record duplication on IngredienLine table.
For example imagine that for recipe_1 an IngredientLine with {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} is associated, if for recipe_5 the IngredientLine child form is compiled by the user with the same values, I don't want a new record on IngredientLine table, but only a new association record in the join table ingredient_lines_recipes.
Note that currently I dont't have any IngredientLine controller as saving and updating IngredientLines is handled by nested form routines. Even my Recipe controller is plain and standard:
class RecipesController < ApplicationController
respond_to :html
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(params[:recipe])
flash[:notice] = 'Recipe saved.' if #recipe.save
respond_with(#recipe)
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
respond_with(:recipes)
end
def edit
respond_with(#recipe = Recipe.find(params[:id]))
end
def update
#recipe = Recipe.find(params[:id])
flash[:notice] = 'Recipe updated.' if #recipe.update_attributes(params[:recipe])
respond_with(#recipe)
end
end
My guess is that should be enough to override the standard create behavior for IngredientLine with find_or_create, but I don't know how to achieve it.
But there's another important point to take care, imagine the edit of a child form where some IngredientLines are present, if I add another IngredientLine, which is already stored in IngredientLine table, rails of course should not write anything on IngredientLine table, but should also distinguish between child records already associated to the parent, and the new child record for which needs to create the relation, writing a new record on the join table.
Thanks!
in Recipe model redefine method
def ingredient_lines_attributes=(attributes)
self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
end
Old question but I had the same problem. Forgot to add :id to white list with rails 4 strong_parameters.
For example:
widgets_controller.rb
def widget_params
params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
end
widget.rb
class Widget < ActiveRecord::Base
has_many :foos, dependent: :destroy
accepts_nested_attributes_for :foos, allow_destroy: true
end
foo.rb
class Foo < ActiveRecord::Base
belongs_to :widget
end
I have run into a similar situation and found inspiration in this answer. In short, I don't worry about the duplication of nested models until save time.
Translated to your example, I added autosave_associated_records_for_ingredient_lines to Recipe. It iterates through ingredient_lines and performs a find_or_create as your intuition said. If ingredient_lines are complex, Yuri's first_or_initialize approach may be cleaner.
I believe this has the behavior you're looking for: nested models are never duplicated, but editing one causes a new record rather than updating a shared one. There is the strong possibility of orphaned ingredient_lines but if that's a serious concern you could choose to update if that model has only one recipe with an id that matches the current one.

Troublesome Wice::WiceGridArgumentError

I am working on a Rails 3.1.1 app that is using WICE_GRID and I am stuck on this error.
I want to show a grid of Roles on the User show page. I am setting up the data in the controller like this.
User and Role are related by has_many through user_role.
def show
#user = User.find(params[:id])
#roles = initialize_grid(#user.roles)
end
When I run the site I get this error
Wice::WiceGridArgumentError in UsersController#show
WiceGrid: ActiveRecord model class (second argument) must be a Class derived from ActiveRecord::Base
The error is pointing to #roles = init.... line. initialize_grid does take a record arguent but that is a hash of options, not an activerecord model collection.
When I run the code in the console I see that #user.roles is
[#<Role id: 1, title: "Role1>, #<Role id: 2, title: "Role2">]
Looks like an ActiveRecord collection to me.
Any help gratefully accepted!
initialize_grid takes a class. You're passing in an array of objects. It appears you want to display a user's roles in the grid. You want something like this:
def show
#user = User.find(params[:id])
#roles = initialize_grid(Role, :conditions => ['user_id = ?', #user.id])
end
However, I'm guessing your roles table doesn't have user_id in it. You likely have a mapping table called user_roles. In which case, you will want to refactor the code above. Try just running this code instead to make sure you can view Roles in a grid (unscoped).
def show
#user = User.find(params[:id])
#roles = initialize_grid(Role)
end

Rails 3.1 - Binding HABTM from other controller

I have:
class Person < ActiveRecord::Base
has_many :people_phones
has_many :phones, :through => :people_phones
end
I also have:
class Request < ActiveRecord::Base
belongs_to :person
belongs_to :phone
end
Now when someone call with a request I open "requests#new" form, fill in person_id and phone_number and other details and submits them to "requests#create" controller#action.
In the "requests#create", I can do:
#phone = Phone.find_or_create_by_phone_number(params[:phone][:phone_number])
But how can I bind Person with that Phone from this Requests controller?
I mean create a record in people_phones table (if it doesn't exists)?
User.find(person_id).phones << #phone
I don't really know how your app works, but you see the idea.
If you have a request, and you want to "validate" it, you would do
request.person.phones << request.phone
Interesting stuff to know, it's kind of related (I'll try to find where I found that, it was a long time ago)
Steps required for the object to be saved to database:
New
Blog.new(…).save
user.blogs << Blog.new(…)
user.blogs.new(…).save – do not use, no practical use case
Build
Blog.build – not possible
user.blogs.build(…), user.save – both are required to save to DB
Create
Blog.create(…)
user.blogs.create(…)

Changing associated objects don't get save with rails model object?

having this code block of an example rails model class:
class Block < ActiveRecord::Base
has_many :bricks, :autosave => true
def crunch
bricks.each do |brick|
if brick.some_condition?
brick.name = 'New data'
brick.save # why do I have to call this?
end
end
end
end
class Brick < ActiveRecord::Base
belongs_to :block, :autosave => true
end
I found that the only way to make sure the changes within the associated objects get saved for me, was to call brick.save manually. Even thought I use :autosave => true
Why?
Probably the autosave option has a misleading name. By the way, it's the expected behaviour. The option is meant for association. So if you modify an object in a relation and save the other object then ActiveRecord saves the modified objects. So, in your case, you could change your code to:
def crunch
bricks.each do |brick|
if brick.some_condition?
brick.name = 'New data'
end
end
save # saving the father with autosave should save the children
end
You could use any of the helper methods available: update_attribute, update_attributes, update_column...
More info: Rails: update_attribute vs update_attributes

Resources