Rails - avoid autosave in association - ruby-on-rails-3

My models and its associations are:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
validates :commenter, :presence => true
end
Case1: Automatically save method is called when I tried below code.
#post = Post.find(3)
#comments = #post.comments
p #comments #=> []
p #comments.class #=> Array
if #comments.empty?
3.times do
#comments << #post.comments.build
end
end
p #comments.first.errors #=>{:commenter=>["can't be blank"]}
Case2: if I manually initialize same empty array to the #comments, auto save is not calling. for instance,
p #comments #=> []
p #comments.class #=> Array
if #comments.empty?
#comments = []
p #comments #=> []
3.times do
#comments << #post.comments.build
end
end
p #comments.first.errors #=>{}
What is the best solution to avoid auto save and please any one explain why the above code behave differently?

Rails extensively uses monkey-patching, so rails Array is not the same thing as pure Ruby array. (Compare output from irb > [].methods and rails c > [].methods
According to the documentation << method of has_many collection
instantly fires update sql without waiting for the save or update call
on the parent object
So most likely Rails have an "observer" of the collection events, and fires validation when you try to add new object.
In second snippet you use pure array (not has_many collection), so the update action is not fired.
To avoid update action you don't need << at all
#post = Post.find(3)
#comments = #post.comments
if #comments.empty?
3.times do
#post.comments.build
end
end
p #comments.size
=> 3

Autosave is defined in the Post model. Read here about Autosave. If I understand your question correctly, then it should be enough to define :autosave => false.

Related

Rails: Setting Model Attributes to Attributes from Another Model

I am a little unsure of how to ask this so I apologize for the clunky explanation.
I have three models, User, Waterusage and Goals
class Goal < ApplicationRecord
belongs_to :user
end
class Waterusage < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable,
has_one :waterusage, :dependent => :destroy
has_one :goals, :dependent => :destroy
end
Waterusage is filled out first by users and then goals. Goals is the exactly same schema as waterusage, but uses a portion of the waterusage form and copies the remaining attributes from waterusage.
class Goal < ApplicationRecord
belongs_to :user
# before_validation :get_from_waterusage
before_validation :calculate_totals
def get_from_waterusage
self.household_size = #waterusage.household_size
self.swimming_pool = #waterusage.swimming_pool
self.bathroom_sink_flow_rate = #waterusage.bathroom_sink_flow_rate
self.low_flow_toilet = #waterusage.low_flow_toilet
self.kitchen_sink_usage = #waterusage.kitchen_sink_usage
self.kitchen_sink_flow_rate = #waterusage.kitchen_sink_flow_rate
self.dishwasher_rate = #waterusage.dishwasher_rate
self.dishwasher_multiplier = #waterusage.dishwasher_multiplier
self.laundry_rate = #waterusage.laundry_rate
self.laundry_multiplier = #waterusage.laundry_multiplier
self.lawn_size = #waterusage.lawn_size
self.carwash_rate = #waterusage.carwash_rate
self.carwash_multiplier = #waterusage.carwash_multiplier
self.miles = #waterusage.miles
self.statewater = #waterusage.statewater
self.percent_statewater = #waterusage.percent_statewater
self.pet_cost = #waterusage.pet_cost
end
...
end
Here is the GoalsController
class GoalsController < ApplicationController
before_action :authenticate_user!
def new
#goal = goal.new
end
def create
##user = User.find(params[:user_id])
#goal = current_user.create_goal(goal_params)
redirect_to goal_result_path
end
def destroy
#user = User.find(params[:user_id])
#goal = #user.goal.find(params[:id])
#goal.destroy
redirect_to user_path(current_user)
end
def show
#goal = goal.find(params[:id])
end
def results
if current_user.goal.get_individual_total > 6000
#temp = 6000
else
#temp = current_user.goal.get_individual_total
end
#goal = current_user.goal
end
private
def goal_params
params.require(:goal).permit(:household_size, :average_shower,
:shower_flow_rate, :bath_rate, :bath_multiplier,
:bathroom_sink_usage,
:bathroom_sink_flow_rate, :mellow, :low_flow_toilet,
:kitchen_sink_usage,
:kitchen_sink_flow_rate, :dishwasher_rate,
:dishwasher_multiplier,
:dishwasher_method, :laundry_rate, :laundry_multiplier,
:laundry_method,
:greywater, :lawn_rate, :lawn_multiplier, :lawn_size,
:xeriscaping,
:swimming_pool, :swimming_months, :carwash_rate,
:carwash_multiplier,
:carwash_method, :miles, :statewater, :percent_statewater,
:shopping,
:paper_recycling, :plastic_recycling, :can_recycling,
:textile_recycling,
:diet, :pet_cost, :individual_total, :household_total,
:home_usage, :outdoor_usage,
:individualDifference, :householdDifference, :vehicle_usage,
:power_usage, :indirect_source_usage,
:individualDifference, :householdDifference)
end
end
I currently have the following error:
NameError in GoalsController#create
undefined local variable or method `current_user' for #
<Goal:0x007fbedde9a590>
It seems to be in the way I am retrieving the info from the waterusage model with
self.household_size = #waterusage.household_size
It there a join I could use?
The waterusage model works BTW.
Thanks
Don't know if it's the best way to do that, but I would use something like this:
In your goals model, you can check if its user have a waterusage already. If it has, you fill the values from that water usage
You can do it using after_initialize callback. In your goal model, would be something like
class Goal < ApplicationRecord
belongs_to :user
after_initialize :set_default_values
def set_default_values
waterusage = self.user.try(:waterusage)
if waterusage
self.attribute1 = waterusage.attribute1
self.attribute2 = waterusage.attribute2
self.attribute3 = waterusage.attribute3
#and it goes on...
end
end
end
so, like this when you do a Goal.new, it will check for a waterusage for that user and initialize those values on your goal. So you don't have to change anything on your controller and even if you do it on console, it will work. Guess it's a better practice to do that using models callbacks. Don't know if it solves your problem, but give it a try. Good luck!
Your error message is:
NameError in GoalsController#create
undefined local variable or methodcurrent_user' for #
Goal:0x007fbedde9a590`
The current_user object is automagically defined inside your controller by the Devise gem you're using. It will not be defined inside your models.
One of your comments includes the following snippet you say you're using from within your Goal model: current_user.waterusage.household_size. That is what your error message is referring to. (Note that this snippet from one of your comments disagrees with the code in your original post. This makes it harder to be certain about what is going wrong here.)

Rails form to edit JSON object as text

I'd like to make a form that lets a user edit one field of a mongoid object as rendered JSON text. There's a field in the model that my rails app should not understand, but I want to expose a generic editor. So for this field, I'd like to render it as pretty JSON, and expose it in a big <textarea> and then parse the JSON back in after any edits.
I can think of a dozen ways to do this, but I'm wonder what would be most consistent with Rails philosophy and least divergent from normal scaffolding. Should I render the object to JSON text in the controller? Then I'd have to repeat that code in the new and edit methods, and the parsing code in the update and create methods, which seems a bit kludgy. Is there a way to define a helper or custom form widget that goes in the _form.html.erb that is more reusable? Or maybe one already written?
You can make your own attribute writer/reader, in the model:
attr_accessible the_field_raw
def the_field_raw
self.the_field.to_s
end
def the_field_raw=(value)
self.the_field = JSON(value)
end
whitch should be compatible with form generators and no extra code in the controllers.
Hope it helps!
Serialize the values as JSON.
class Price < ActiveRecord::Base
serialize :values, JSON
validates :start, :end, :values, :presence => true
end
migration:
class CreateMyModels < ActiveRecord::Migration[7.0]
def change
create_table :my_models do |t|
t.jsonb :name, default: {}, null: false
t.jsonb :description, default: {}, null: false
t.integer :another_param
t.timestamps
end
end
end
model and concern:
class MyModel < ApplicationRecord
AVAILABLE_LOCALES = I18n.available_locales
include JsonLocalize
json_localize :name, :description
end
module JsonLocalize
extend ActiveSupport::Concern
included do
def self.json_localize(*attrs)
self::AVAILABLE_LOCALES.each do |locale|
attrs.each do |attr|
define_method("#{attr}_#{locale}") do
send(attr)[locale.to_s]
end
define_method("#{attr}_#{locale}=") do |value|
send(attr)[locale.to_s] = value
end
end
end
end
end
end
then you can have in your form:
.row
.col-md-6
- MyModel::AVAILABLE_LOCALES.each do |loc|
= f.input "name_#{loc}"
= f.input "description_#{loc}"
controller params:
def resource_params
params.require(:my_model).permit(
[
:another_param
] | [:name, :description].map {|attr| MyModel::AVAILABLE_LOCALES.map { |loc| "#{attr}_#{loc}".to_sym } }.flatten
)
end

Rails 3: Find parent of polymorphic model in controller?

I'm trying to find an elegant (standard) way to pass the parent of a polymorphic model on to the view. For example:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
The following way (find_imageable) works, but it seems "hackish".
#PictureController (updated to include full listing)
class PictureController < ApplicationController
#/employees/:id/picture/new
#/products/:id/picture/new
def new
#picture = imageable.pictures.new
respond_with [imageable, #picture]
end
private
def imageable
#imageable ||= find_imageable
end
def find_imageable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
Is there a better way?
EDIT
I'm doing a new action. The path takes the form of parent_model/:id/picture/new and params include the parent id (employee_id or product_id).
I'm not sure exactly what you're trying to do but if you're trying to find the object that 'owns' the picture you should be able to use the imageable_type field to get the class name. You don't even need a helper method for this, just
def show
#picture = Picture.find(params[:id])
#parent = #picture.imagable
#=> so on and so forth
end
Update
For an index action you could do
def index
#pictures = Picture.includes(:imagable).all
end
That will instantiate all 'imagables' for you.
Update II: The Wrath of Poly
For your new method you could just pass the id to your constructor, but if you want to instantiate the parent you could get it from the url like
def parent
#parent ||= %w(employee product).find {|p| request.path.split('/').include? p }
end
def parent_class
parent.classify.constantize
end
def imageable
#imageable ||= parent_class.find(params["#{parent}_id"])
end
You could of course define a constant in your controller that contained the possible parents and use that instead of listing them in the method explicitly. Using the request path object feels a little more 'Rails-y' to me.
I just ran into this same problem.
The way I 'sort of' solved it is defining a find_parent method in each model with polymorphic associations.
class Polymorphic1 < ActiveRecord::Base
belongs_to :parent1, :polymorphic => true
def find_parent
self.parent1
end
end
class Polymorphic2 < ActiveRecord::Base
belongs_to :parent2, :polymorphic => true
def find_parent
self.parent2
end
end
Unfortunately, I can not think of a better way. Hope this helps a bit for you.
This is the way I did it for multiple nested resources, where the last param is the polymorphic model we are dealing with: (only slightly different from your own)
def find_noteable
#possibilities = []
params.each do |name, value|
if name =~ /(.+)_id$/
#possibilities.push $1.classify.constantize.find(value)
end
end
return #possibilities.last
end
Then in the view, something like this:
<% # Don't think this was needed: #possibilities << picture %>
<%= link_to polymorphic_path(#possibilities.map {|p| p}) do %>
The reason for returning the last of that array is to allow finding the child/poly records in question i.e. #employee.pictures or #product.pictures

Rails 3 noob - difficulty populating an array

UPDATED WITH SOLUTION FROM BELOW PRODUCES NEW ERROR.
Users invite others to review their work. To track this, the invitations table has reviewer_id and reviewee_id fields. The model contains:
belongs_to :user
has_many :users
To display all invitations for a user we first get all the invitations:
#invitations = Invitation.where("reviewee_id = ?", current_user.id ).select(:reviewer_id).order("updated_at")
Then we get all the users who were invited: (this part is wrong)
#reviewers = []
#invitations.each do |i|
#reviewers << User.where("id = ?", i.reviewer_id )
end
This current solution produces the following:
undefined method `first_name' for []:ActiveRecord::Relation
I did a test with the following code to see what is in #reviewers:
<% #reviewers.each do |r| %>
<%= r.id %><br>
<% end %>
Instead of returning the ids it returned:
2173491700
2173491200
2173490540
So the array is not getting populated appropriately.
I am grateful for your help and most grateful for specific code.
You want to gather up all the IDs then pass them to where.
reviewer_ids = #invitations.collect { |i| i.reviewer_id }
#reviewers = User.where(:id => reviewer_ids)
This grabs all the reviewers in a single database call.
Do the following to get the reviewers:
#reviewers = []
#invitations.each do |i|
#reviewers << User.where("id = ?", i.reviewer_id )
end
The << adds elements to the array. And, before the iteration we create an array.

best way to save dependent objects in a has_and_belongs_to_many relation?

Hi I am new to rails and I would like to know what is the best way who save dependent objects in an HBTM relation.
Specifically, I have two classes Post and Tag
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
I have a migration to create the joining table
class AddPostsTagsJoinTable < ActiveRecord::Migration
def self.up
create_table :posts_tags, :id => false do |t|
t.integer :post_id
t.integer :tag_id
end
end
def self.down
drop_table :postss_tags
end
end
All is good up to here
So I have a PostsController from which I handle the creation, updates and deletes for the posts, and I want to encapsulate the Tags so that the creation is via the PostsController... like so:
class PostsController < ApplicationController
#... code removed for brevity
def create
#post = current_user.posts.build(params[:post])
if #post.save
tag_names = params[:post][:tags].strip.split(' ')
tag_names.each do |t|
#see if the tag already exists
tag = Tag.find_by_name(t);
if tag.nil?
#post.tags.create!(:name => t)
else
#post.tags << tag #just create the association
end
end
flash[:success] = "Post created."
redirect_to(user_posts_path(current_user.username))
else
#user = current_user
render 'new'
end
end
end
I am not sure how I should handle the creation of my Tag(s) because if I just call
#post.tags.create!(:name => t)
this will create duplicate records in the Tags table (even when :uniq => true is specified in the model).
So to avoid the duplication I see if a tag is already present and then add it like this
tag = Tag.find_by_name(t);
if tag.nil?
#post.tags.create!(:name => t)
else
#post.tags << tag #just create the association
end
Is this the way it's supposed to be done?
This seems expensive (especially 'cause it's in a loop) so I am wondering if there is another "cleaner" way to do this? (pls forget the DRY'ing up of the action and so on)
Is there a clean way to create my Tags without having to manually check for duplicates?
thank you in advance for your help!
You can save tags attribute of post if automatically by adding accepts_nested_attributes_for to Post model
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
accepts_nested_attributes_for :tags
end
The next step is to output tags fields inside post form.