Setter method being called before create action - ruby-on-rails

I have a new form that creates an Item (all the codes are obviously simplified):
<%= simple_form_for #item do |f| %>
<%= f.input :brand_name %>
<%= f.button :submit %>
<% end %>
The current user will create an item and link it to a new or to an existing brand.
This field doesn't exist in the database; it'll be used as a way to associate all models. Hence, I create its getter and setter.
def Item < ActiveRecord::Base
belongs_to :user
belongs_to :brand
attr_accessible :brand_name
def brand_name
brand.try :name
end
def brand_name=(name)
if name.present?
brand = user.brands.find_or_initialize_by_name(name)
brand if brand.save
end
end
end
class ItemsController < ApplicationController
def new
#item = current_user.items.build
end
def create
#item = current_user.items.build(params[:item])
if #item.save
...
end
end
end
The problem is that when the form is submitted, I get this error, which lies in the product_name=() method. I've done some debugging through Rails' console and it goes all fine, but in the browser the setter method is called before the create action. That is, the record doesn't even have a user associated to it. I tried leaving the create method empty, for example, but nothing different happens.
undefined method `brands' for nil:NilClass
What is really weird is that this was working a couple of weeks ago (I've checked my git commits and the code is identical).
I though about calling the before_create callback, but there's no way to know which user should be linked.
UPDATE
I'm using Sorcery as the authentication handler. Everything always works fine, except for this create action.
class User < ActiveRecord::Base
authenticates_with_sorcery!
belongs_to :company
has_many :items
end
class Company < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :brands, dependent: :destroy
end

Related

Rails Mass Assign has_and_belongs_to_many

I'm getting this error when I try to mass-assign a HABTM relationship in Rails 5:
*** ActiveRecord::RecordNotFound Exception: Couldn't find Job with ID=6 for Profile with ID=
class Profile < ApplicationRecord
has_and_belongs_to_many :jobs
accepts_nested_attributes_for :jobs
end
class Job < ApplicationRecord
has_and_belongs_to_many :profiles
end
class ProfilesController < ApplicationController
def create
#profile = Profile.new(profile_params)
end
private
def profile_params
params.require(:profile).permit(
:name,
:email,
:jobs_attributes: [:id]
)
end
end
=form_for #profile do |f|
=f.fields_for :jobs do |j|
=j.select :id, options_for_select([[1, "Job 1", ...]])
The problem is that accepts_nested_attributes_for is a way to update attributes on an associated object (either already linked or one that you're creating). You can think of it doing something like:
params[:jobs_attributes].each do |job_attributes|
#profile.jobs.find(job_attributes[:id]).attributes = job_attributes
end
Except that the job is then saved with the new attributes in an after-save of the parent object.
This isn't what you want. In fact, you don't need accepts_nested_attributes at all.
Instead, change the attribute in your view to job_ids as if it's an attribute of the profile, something like the following:
=form_for #profile do |f|
=j.select :job_ids, options_for_select([[1, "Job 1", ...]])
This will effectively call profile.job_ids = [1,2,3] which will have the effect that you're looking for.

uninitialized constant ReviewsController::Reviews

I had this working very similar to another controller however i needed to change this relation to another controller called agreements_controller. I want to create a has one model. review has one and belongs to agreements.
Why isn't the row being created properly?
reviews_controller:
class ReviewsController < ApplicationController
def create
#review = Reviews.create(review_params)
end
private
def review_params
params.require(:review).permit(:comment, :star, :agreement_id, :user_id, :reviser_user_id)
end
end
_form.html.erb
<%= form_for([agreement, agreement.build_review] ) do |f| %>
<% end %>
agreement.rb
class Agreement < ActiveRecord::Base
has_one :review, :dependent => :destroy
end
review.rb
class Review < ActiveRecord::Base
belongs_to :agreement
belongs_to :reviser_user
belongs_to :user
end
I've tried to find similar examples online, but all I could find was nested forms... I don't need a nested form I just want the review to create as a has one.
Models are Singular. Use
Review.create(review_params)

Rails: How to trigger the after_destroy callback when deleting nested join model records

I've 3 models User, Feature, UserFeature
class User < ActiveRecord::Base
has_many :user_features
has_many :features, through: :user_features
end
class Feature < ActiveRecord::Base
has_many :user_features
has_many :users, through: :user_features
end
class UserFeature < ActiveRecord::Base
belongs_to :user
belongs_to :feature
end
I've already created many features in DB, and associated features with user while creating user with following code
<%= form_for #user do |f| %>
// some user_fields
<% Feature.all.each do |feature| %>
<%= check_box "user[feature_ids][], feature.id %>
<% end %>
// submit button here
<% end %>
In my UserController I've this code
class UserController < ApplicationController
def create
#user = User.new(permit_params)
#user.save
end
def update
#user = User.find(params[:id])
#user.update_attributes(permit_params)
end
private
def permit_params
params.require(:user).permit(:name, :email, user_feature_ids: [])
end
end
When I submit it will create and update the user and make entries in UserFeature Table for those features that I've checked.
When update user if I unchecked any feature then it will delete relative record from UserFeature
Here don't have any issue everything works as expected.
But now I want to perform some activity when a user_feature is delete.
For this I wrote a callback in UserFeature after_destroy :some_activity
class UserFeature < ActiveRecord::Base
belongs_to :user
belongs_to :feature
after_destroy :some_activity
def some_activity
// some code
end
end
but its not working, when I checked why its not calling destroy call while deleting user_feature, I found that it will call a SQL query instead of calling destroy on unchecked user_feature.
That's why after or before destroy callback not working.
Could any one let me know, how can I perform any activity while deleting UserFeature?
The after_destroy callback is not fired after delete, because this is how delete is implemented. Quote from the docs:
Active Record objects are not instantiated, so the object’s callbacks are not executed, including any :dependent association options. [...] Note: Although it is often much faster than the alternative, #destroy, skipping callbacks might bypass business logic in your application that ensures referential integrity or performs other essential jobs.
Instead of an after_destroy callback on the association class you can use an after_remove callback on the association definition, like described in the Rails Guide about Association Callbacks.
The solution is to not set up your code to use the "Automatic deletion of a join model" mode (because this mode won't trigger the callback for the deletion).
Instead, set up your code as a standard nested resource using the accepts_nested_attributes_for declaration, which will trigger the callback for the deletion.
The models would be:
class User < ActiveRecord::Base
has_many :user_features
accepts_nested_attributes_for :user_features, allow_destroy: true
end
class Feature < ActiveRecord::Base
has_many :user_features
end
class UserFeature < ActiveRecord::Base
belongs_to :user
belongs_to :feature
after_destroy :some_activity
def some_activity
# Some code here to be executed after the Object destruction
end
end
Note:
You don't need the through: in the has_many declaration, because you won't be using the getter/setter .feature_ids that would come with it.
You do need the accepts_nested_attributes_for declaration with the allow_destroy: true option.
The form would be:
<%= form_for #user do |user_form| %>
// Any User fields here
<%= user_form.fields_for :user_features, #user_features do |user_feature_form| %>
// The UserFeature check_box field:
<%= user_feature_form.check_box :_destroy,
{ checked: user_feature_form.object.persisted?,
class: 'my_class' },
checked_value = false,
unchecked_value = true %>
// Any other UserFeature fields here, i.e.:
<%= user_feature_form.number_field :quantity,
class: 'my_class' %>
<% end %>
// Submit button here
<% end %>
Note:
Use the .fields_for method for the generation of the UserFeature fields.
The check_box must have the method :_destroy.
checked_value must be false (if checked, then don't delete)
unchecked_value must be true (if unchecked, then do delete)
The (basic) Controller would be:
class UserController < ApplicationController
def create
#user = User.new(permit_params)
#user.save
end
def new # or: def edit
#user = User.find(params[:id])
Feature.all.each do |feature|
user_feature = #user.user_features.select {|u_f| u_f.feature_id == feature.id}[0]
# If no corresponding UserFeature exists, build it (= non-persisted, which will determine if the check_box is checked or not)
if user_feature.nil?
#user.user_features.build(feature_id: feature.id, quantity: nil)
end
end
# Optional: Sort (i.e. by Feature.name) to keep the list order fixed (pass this array to the form)
#user_features = #user.user_features.sort_by {|u_f| u_f.feature.name}
end
def update
#user = User.find(params[:id])
#user.update_attributes(permit_params)
end
private
def permit_params
params.require(:user).permit(:name, :email, user_features_attributes: [
:id, :quantity, :_destroy
])
end
end

Rails form_for and has_many through argument error

I'm trying to build a form_for to create a join model between two other models. I have a Book model and User model, with another called Reads that is my join. Here is how I've set up the associations:
class User < ActiveRecord::Base
has_many :reads
has_many :books, :through => :reads
end
class Book < ActiveRecord::Base
has_many :reads
has_many :users, :through => :reads
end
class Read < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
I've looked at the docs for form_for and watched the railscast episode on many-to-many associations, but I can't figure out why I'm getting the error when I try to render the Book#show view where I've put the form:
First argument in form cannot contain nil or be empty
Here is my form in app/views/books/show.html.erb:
<%= form_for(#read) do |f| %>
<%= f.hidden_field :book_id, value: #book.id %>
<%= button_to 'Add to Reads', {controller: 'reads', action: 'create'}, {class: 'btn'} %>
<% end %>
I think part of the problem is that I am trying to create a 'Reads' object from the Books model, but I'm not sure what I am doing wrong. I need the 'Add to Reads' button on the Book's page so that a user can select that particular book to add to their 'reads.' I'm also adding the current_user id in the controller, rather than in the view. Here is my create action from the Reads controller if that helps...
def create
#read = Read.new(read_params)
#read.user_id = current_user.id
#read.save
if #read.save
# do this
else
# do that
end
end
And I'm using strong params...
def read_params
params.require(:read).permit(:user_id, :book_id)
end
Thanks for any help.
First argument in form cannot contain nil or be empty
This means that #read in your form is nil. Since you are in the show action of your Books controller, you have to define this variable in the books controller.
def show
#read = Read.new
...
end

Ruby on Rails - Need help associating models

Ok, am still a newbie in ruby on rails trying to learn my way around. I have two models (User model and Comment model). Basically a user has a simple profile with an 'about me' section and a photo's section on the same page. Users must be signed in to comment on other users profiles.
My User Model
class User < ActiveRecord::Base
attr_accessible :email, :name, :username, :gender, :password, :password_confirmation
has_secure_password
has_many :comments
.
.
end
My Comment Model
class Comment < ActiveRecord::Base
belongs_to :user
attr_accessible :content
.
.
end
In my comments table, I have a user_id column that stores the id of the user whose profile has been commented on and a commenter_id column that stores the id of the user commenting on the profile.
Comment Form
<%= form_for([#user, #user.comments.build]) do |f| %>
<%= f.text_area :content, cols: "45", rows: "3", class: "btn-block comment-box" %>
<%= f.submit "Comment", class: "btn" %>
<% end %>
My comments Controller
class CommentsController < ApplicationController
def create
#user = User.find(params[:user_id])
#comment = #user.comments.build(params[:comment])
#comment.commenter_id = current_user.id
if #comment.save
.........
else
.........
end
end
end
This works fine storing both user_id and commenter_id in the database. My problem comes when displaying the user comments on the show page. I want to get the name of the user who commented on a specific profile.
In my user controller
def show
#user = User.find(params[:id])
#comments = #user.comments
end
I want to get the name of the user from the commenter_id but it keeps throwing errors undefined method 'commenter' for #<Comment:0x007f32b8c37430> when I try something like comment.commenter.name. However, comment.user.name works fine but it doesn't return what I want. Am guessing am not getting the associations right.
I need help getting the correct associations in the models so as to get the name from the commenter_id.
My last question, how do I catch errors in the comments form? Its not the usual form_for(#user) where you do like #user.errors.any?.
routes.rb
resources :users do
resources :comments, only: [:create, :destroy]
end
Try something like this in your models
class User < ActiveRecord::Base
has_many :received_comments, :class_name => "Comment", :foreign_key => "user_id"
has_many :given_comments, :class_name => "Comment", :foreign_key => "commenter_id"
end
class Comment < ActiveRecord::Base
belongs_to :user # comment about profile
belongs_to :commenter, :class_name => "User", :foreign_key => "commenter_id"
end
check out: http://guides.rubyonrails.org/association_basics.html
you can probably come up with better naming on the has_many collections, received and given were the best I could do on short notice :)
Note: foreign_key is option in many cases, left it in above - i think it helps with clarity
has_many fk refers to the the column in the many table (other table)
belongs_to fk refers to the column in the many table (this table)

Resources