build automatically nested object - ruby-on-rails

in my client_controller.rb
def edit
#client = Client.find(params[:id])
#client.build_address unless #client.address
...
end
...address is nested polymorphic attribute (1:1)
I don't like to call the build attribute in controller
my question:
is it good idea to automaticly build nested object if it isn't build yet ?
example:
class Client
has_one :address, :as => :addressable #polymorphic
#...
def address
super || build_address
end
end
question2:
is there better way to do that ?

Implemented like that in project form more than 6 weeks, still no problem at all,
so yeah looks like a good idea in my case, see question comments

Related

Ruby on rails: array at initialize

I'm working with rails 5.1.2 and ruby 2.2.6
I have the following classes:
class Idea < ApplicationRecord
validates :title, presence: true
validates :description, presence: false
has_many :discussions
end
class Discussion < ApplicationRecord
validates :title, presence: true
belongs_to :idea
def initialize(title)
#title = title
end
end
At idea creation, I'd like to add a default discussion in the attribute discussions. As I'm a newbie at ruby and rails, I don't know which is the best approach to do this. Here is what I tried unsuccessfully.
In the idea controller, I tried to create the default discussion at the idea creation, as follows:
class IdeasController < ApplicationController
def create
discussion = Discussion.new "Main thread"
#idea = Idea.new(idea_params)
#idea.discussions << discussion
#idea.save
redirect_to ideas_path
end
private
def idea_params
params.require(:idea).permit(:title)
end
end
This drives me to an error in the controller:
undefined method `<<' for nil:NilClass
on the line
#idea.discussions << discussion
I think this is due to an uninitialized discussions array in my idea. However, the guide states that any class that has the declaration has_many would inherit the method <<, as stated in this guide. But maybe this is only true after the idea has been saved at least one time?
I tried manually initialize the array in my controller.
#idea.discussions = []
This helps removing the error, but I'm surprised this is not done automatically. Furthermore, the discussion is not saved in database. I tried adding the declaration autosave in Idea class, with no effect:
has_many :discussions, autosave: true
I'm a little bit lost. At the end, I'd just like to add a discussion in an idea between its creation and save, and persist it. What is the best approach?
Thanks for any help.
Discussion is already an ActiveRecord object, so you don't need the initialize method. Simply calling Discussion.new should work out of the box.
To build a default Discussion when creating an Idea just do this:
#idea.build_discussion . This is will instantiate a new Discussion association on your Idea model. When you save Idea, it will automatically save the Discussion object as well and automatically associate it to that Idea.
Edit: To simplify the answer, here's the code:
def create
#idea = Idea.new
#idea.build_discussion(title: 'Main Thread')
if #idea.save
redirect_to ideas_path
else
redirect_to :new
end
end
Edit 2: And because you build the Discussion through Idea, you need to add this to your IdeaController strong_params:
def idea_params
params.require(:idea).permit(
...
discussion_attributes: [
:id,
:title,
..
]
)
end
Edit 3: Sorry, I didn't pay attention to your association type. Update to this:
def create
#idea = Idea.new
#idea.discussions.new(title: 'Main Thread')
if #idea.save
redirect_to ideas_path
else
redirect_to :new
end
end
First off, don't override initialize in an ActiveRecord model unless you know what you're doing. Your object already has an initialize method defined, you just can't see it because it's inherited. If you override without accepting the right set of parameters and calling super you will introduce bugs.
ActiveRecord gives you an easy hash syntax for setting attributes at initialize already. You can do Discussion.new(title: 'Title') right out of the box.
If you always want your ideas to be created with a default discussion you can move this down to the model in a before_create callback.
class Idea < ApplicationRecord
validates :title, presence: true
validates :description, presence: false
has_many :discussions
before_create :build_default_discussion
private
def build_default_discussion
discussions.build(title: 'Main Thread')
end
end
Here you're calling the private method build_default_discussion before every new idea is persisted. This will happen automatically when you create a new Idea either with Idea.new.save or Idea.create or any other proxy method that creates a new Idea, anywhere in your application.

Rails nested form - refactor create action | cocoon gem

Everything is working fine but I want to change the code in create action to something like in update action. Right now, in the create action I am looping through all the values and saving them, but want to do it in a single line.
I have a College model.
class College< ActiveRecord::Base
has_many :staffs, dependent: :destroy
accepts_nested_attributes_for :staffs, reject_if: :all_blank, allow_destroy: true
end
And this is my Staff.rb
class Staff < ActiveRecord::Base
belongs_to :college
end
And these are my Staffs controller create and update actions
def create
#college= College.find(params[:college][:id_college_profile]
)
staff_params = params[:college][:staffs_attributes].values
staff_params.each do |staff_param|
#staff = #college.staffs.new
#staff.name = staff_param[:name]
#staff.designation = staff_param[:designation]
#staff.experience = staff_param[:experience]
#staff.specialization = staff_param[:specialization]
#staff.save
end
redirect_to dashboard_path(id: #college.id), notice: "Successfully added Staff."
end
def update
#college= College.find(params[:college][:id_college]
)
#college.update_attributes(staff_parameters)
redirect_to root_path
end
These are strong parameters
def staff_parameters
params.require(:college).permit(:id, staffs_attributes: [:id, :name, :specialization, :experience, :designation, :_destroy])
end
Is there a way to save all of staffs in create action, without looping through all the values, but save all of them with a single line of code as in update action?
I have tried this in the StaffsController create action
def create
#college= College.find(params[:college][:id_college]
)
#staff= #college.staffs.new(staff_parameters)
#staff.save
redirect_to dashboard_path(id: #college.id), notice: "Successfully added Staffs."
end
But it threw this error
unknown attribute 'staffs_attributes' for Staff.
Can someone kindly help me with this issue?
This is a CollegesController so I am assuming the create action also creates the new college?
So in that case your create action should simply be something like:
def create
#college = College.new(staff_parameters)
if #college.save
# succesfully created
else
# there was a validation error
end
end
Note that in general we would use college_parameters because the root element is college and that you not only edit the nested staff, but also possibly attributes from college.
If the college always already exists (because you are doing a find), it is a bit confusing to me what the difference is between create and update and why not always render the edit action in that case?
I have a demo-project show-casing cocoon and nested attributes.
You can do this many ways. The "staff_parameters" method threw an error because you are calling it on class Staff in the create action and on the college class for the update action. Simplest thing to do what you want is to copy the staff parameters method strong parameters and duplicate it. Name this second method create_staff and change the "params.require(:college)" part to "params.require(:staff)" and leave the rest the same. Then in your create action you can do "college.staff(create_staff)". Im on my phone so the formatting isnt good lol i put the code in quotes.

Rails 4 update_attributes with nested attributes not saving

I am trying to break up the registration process of my app into nice bite-size chunks.
So I'm posting forms via Ajax and just trying to update some of the models attributes that were available at that step of that process, basically meaning that they won't be valid at each save point.
As a result, I have been using update_attribute which works fine. However, one of my attributes is a has_many association and I'm struggling to get this working.
I have Channelmodel with has_many :channel_tags, and also accepts_nested_attributes_for :channel_tags. Saving and updating work fine when I use the update method but I cannot get it working with update_attribute or update_attributes.
As far as I can tell, I need to use update_attributes. I wanted to do something like:
#channel.update_attributes(channel_tags_attributes: params[:channel][:channel_tags_attributes])
But this doesn't create the new channel_tags. I have also tried with:
#channel.update_attributes(tag_params)
and:
params.require(:channel).permit(channel_tags_attributes: [ :id, :channel_id, :tag_id, :_destroy ]);
But again, it just doesn't seem to do anything.
When checking from the console, it appears that all of it's happening because that it's loading the Channel for the database and then the category.
Am I doing something wrong, or is there a better way of doing this?
Try to change the name to permitted params method:-
def channel_params
params.require(:channel).permit(channel_tags_attributes: [ :id, :channel_id, :tag_id, :_destroy ]);
end
and user this method in update attributes:-
def update
#channel = Channel.find(params[:id])
if #channel.update_attributes(channel_params)
# add your code here
end
end
As you are trying to do it with nested attributes in rails 4 then your code should look like,
In tag model
has_many :channel_tags
accepts_nested_attributes_for :channel_tags, allow_destroy: true
Controller should look like
def update
#tag = Tag.find(params[:id])
puts "==== #{tag_params.inspect} ===="
puts "==== #{tag_params[:channel_tags].inspect} ===="
if #tag.update!(tag_params)
redirect_path
end
end
private
def tag_params
params.require(:tag).permit(:name ,channel_tags_attributes: [:id, :channel_id, :tag_id, :_destroy])
end
While updating attributes please check the server logs as I have inspect the params which you try to update for the tag attribs.
.update_attributes will only when you want particular attributes. .update will use HASH in params which we are defining as strong params.

Uniqueness error in has_many nested attributes

I have a class student with has_many tests. The test class has a student_id, marks, name. Here the test name should be unique. The test is a nested attribute for student. So the parameters are this way:
:student => {:first_name => "abc",
:email => "dfsdf#sfdsdsd.bbb",
:tests_attributes => { "0" => {:name => "bgc", :marks => "470"}}}
I have a problem with update. If I update_attributes with the tests_attributes, it throws a validation error saying the name for test is not unique. I am actually addressing the same record here. How do I overcome this?
Without seeing your models (& validations), it's going to be quite difficult to diagnose your error directly.
--
Nested Attributes
We've done something like this, and found that your nested data is passed to the child model as if it were receiving a new object (without being nested). This means if you've got validates uniqueness for that model, it should be okay:
#app/models/test.rb
Class Test < ActiveRecord::Base
belongs_to :student
validates :name, uniqueness: true
end
Reason I write this is because there's a method called inverse_of, which basically allows you to access the parent model data in your child model
--
Update
I think the problem will likely lie with your use of update_attributes. Problem being you're trying to update both the student and the test attributes at one time.
I'm not sure exactly why this would be a problem, but I'd test this:
#app/controllers/students_controller.rb
class StudentsController < ApplicationController
def update
#student = Student.find params[:id]
#student.test.update(name: params[:test_name], marks: params[:marks])
end
end
I think if you can explain your methodology a little more, it will be much more helpful. I.E are you trying to update student or test? If you're updating student & adding a new test, how are you updating the studet?
Thanks for the reply guys. I ended up finding the answer myself. I did have a uniqueness validation for name.
I had a situation where initially I wouldn't know the student but have only his details. So I would have to create this hash and pass it to update. The trick to not trying to create a new record for the same name in test is to pass the actual record's ID along with it. This solved the problem
Nested Attributes
I think the problem with nested_attributes. For update need to pass nested_attributes with ID.
Ex.
:student => {:first_name => "abc",
:email => "dfsdf#sfdsdsd.bbb",
:tests_attributes => { "0" => {id: 1, :name => "bgc", :marks => "470"}}}
I have tried below-given example it is worked for me:
Update
#app/controllers/students_controller.rb
class StudentsController < ApplicationController
def update
#student = Student.find params[:id]
#student.update_attributes(student_params)
end
private
def student_params
params.require(:student).permit(:first_name, :email,
tests_attributes: [:id, :name, :marks])
end
end

I feel like this needs to be refactored - any help? Ruby modeling

So let's say you have
line_items
and line_items belong to a make and a model
a make has many models and line items
a model belongs to a make
For the bare example idea LineItem.new(:make => "Apple", :model => "Mac Book Pro")
When creating a LinteItem you want a text_field box for a make and a model. Makes and models shouldn't exist more than once.
So I used the following implementation:
before_save :find_or_create_make, :if => Proc.new {|line_item| line_item.make_title.present? }
before_save :find_or_create_model
def find_or_create_make
make = Make.find_or_create_by_title(self.make_title)
self.make = make
end
def find_or_create_model
model = Model.find_or_create_by_title(self.model_title) {|u| u.make = self.make}
self.model = model
end
However using this method means I have to run custom validations instead of a #validates_presence_of :make due to the associations happening off a virtual attribute
validate :require_make_or_make_title, :require_model_or_model_title
def require_make_or_make_title
errors.add_to_base("Must enter a make") unless (self.make || self.make_title)
end
def require_model_or_model_title
errors.add_to_base("Must enter a model") unless (self.model || self.model_title)
end
Meh, this is starting to suck. Now where it really sucks is editing with forms. Considering my form fields are a partial, my edit is rendering the same form as new. This means that :make_title and :model_title are blank on the form.
I'm not really sure what the best way to rectify the immediately above problem is, which was the final turning point on me thinking this needs to be refactored entirely.
If anyone can provide any feedback that would be great.
Thanks!
I don't think line_items should belong to a make, they should only belong to a model. And a model should have many line items. A make could have many line items through a model. You are missing a couple of methods to have your fields appear.
class LineItem
belongs_to :model
after_save :connect_model_and_make
def model_title
self.model.title
end
def model_title=(value)
self.model = Model.find_or_create_by_title(value)
end
def make_title
self.model.make.title
end
def make_title=(value)
#make = Make.find_or_create_by_title(value)
end
def connect_model_and_make
self.model.make = #make
end
end
class Model
has_many :line_items
belongs_to :make
end
class Make
has_many :models
has_many :line_items, :through => :models
end
It's really not that bad, there's just not super easy way to do it. I hope you put an autocomplete on those text fields at some point.

Resources