class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
params = { :member => {
:name => 'joe', :posts_attributes => [
{ :title => 'Kari, the awesome Ruby documentation browser!' },
{ :title => 'The egalitarian assumption of the modern citizen' },
]
}}
member = Member.create(params['member'])
After doing this I want is to map the elements in posts_attributes in params hash to the id's (The primary keys) after they are saved. Is there any thing I can do when accepts_nested_attributes_for builds or creates each record
PS: posts_attributes array may not contain index in sequence I mean this array might not contain index like 0,1,2 it can contain index like 0,127653,7863487 as I am dynamically creating form elements through javascript
also, I want is to associate only new records created in Post and not already existing Post
Thanks in Advance
Have you considered refreshing the posts association and grabbing the posts_attributes array in full?
Unfortunately, there is not a reliable way to do what you want. You could try looping over both and finding the IDs associated with the content using string matching, but without a field on the posts that is guaranteed to be a unique value, there's not an effective way to do it.
Although I'm not quite sure about what elements you want to assign with what ids, I think this approach would give you a hint.
You may assign a method name symbol to :reject_if, then put your logic into that method, like this:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, :reject_if => :reject_posts?
def reject_posts?(attrs)
# You can do some assignment here
return true if attrs["title"].blank?
post_exist = self.posts.detect do |p|
p.title == attrs["title"]
end
return post_exist
end
end
Related
Could not find nothing close to what I'm trying to do. I want to store an object into a user's column. That column is in the form of an array:
#postgres
def change
add_column :users, :interest, :string, array: true, default: '{}'
end
I have another model called FooBar setup for other use. Each user has unique information inside as I've added a user_id key.
Im trying to make more sense:
def interest
#user = User.find(current_user.id ) # I need the logged in user's id
#support = Support.find(params[:id]) # I need the post's id they are on
u = FooBar.new
u.user_id = #user
u.support_id = #support
u.save # This saves a new Foo object..this is what I want
#user.interest.push(FooBar.find(#user)) # This just stores the object name itself ;)
end
So when I call u1 = FooBar.find(1) I get value return in hash. I want when I say u1.interest I get the same. The reason is, I need to target those keys on the user ie: u1.interest[0].support_id
Is this possible? I've looked over my basic ruby docs and nothing works. Oh..if I passed FooBar.find(#user).inspect I get the hash but not the way I want it.
Im trying to do something similar to stripe. Look at their data key. That's a hash.
Edit for Rich' answer:
I have, literally, a model called UserInterestSent model and table:
class UserInterestSent < ActiveRecord::Base
belongs_to :user
belongs_to :support # you can call this post
end
class CreateUserInterestSents < ActiveRecord::Migration
def change
create_table :user_interest_sents do |t|
t.integer :user_id # user's unique id to associate with post (support)
t.integer :interest_sent, :default => 0 # this will manually set to 1
t.integer :support_id, :default => 0 # id of the post they're on
t.timestamps # I need the time it was sent/requested for each user
end
end
end
I call interest interest_already_sent:
supports_controller.rb:
def interest_already_sent
support = Support.find(params[:id])
u = UserInterestSent.new(
{
'interest_sent' => 1, # they can only send one per support (post)
'user_id' => current_user.id, # here I add the current user
'support_id' => support.id, # and the post id they're on
})
current_user.interest << u # somewhere this inserts twice with different timestamps
end
And the interest not interests, column:
class AddInterestToUsers < ActiveRecord::Migration
def change
add_column :users, :interest, :text
end
end
HStore
I remembered there's a PGSQL datatype called hStore:
This module implements the hstore data type for storing sets of
key/value pairs within a single PostgreSQL value. This can be useful
in various scenarios, such as rows with many attributes that are
rarely examined, or semi-structured data. Keys and values are simply
text strings.
Heroku supports it and I've seen it used on another live application I was observing.
It won't store your object in the same way as Stripe's data attribute (for that, you'll just need to use text and save the object itself), but you can store a series of key:value pairs (JSON).
I've never used it before, but I'd imagine you can send a JSON object to the column, and it will allow you to to use the attributes you need. There's a good tutorial here, and Rails documentation here:
# app/models/profile.rb
class Profile < ActiveRecord::Base
end
Profile.create(settings: { "color" => "blue", "resolution" => "800x600" })
profile = Profile.first
profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
--
This means you should be able to just pass JSON objects to your hstore column:
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def update
#profile = current_user.profile
#profile.update profile_params
end
private
def profile_params
params.require(:profile).permit(:x, :y, :z) #-> z = {"color": "blue", "weight": "heavy"}
end
end
As per your comments, it seems to me that you're trying to store "interest" in a User from another model.
My first interpretation was that you wanted to store a hash of information in your #user.interests column. Maybe you'd have {name: "interest", type: "sport"} or something.
From your comments, it seems like you're wanting to store associated objects/data in this column. If this is the case, the way you're doing it should be to use an ActiveRecord association.
If you don't know what this is, it's essentially a way to connect two or more models together through foreign keys in your DB. The way you set it up will determine what you can store & how...
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :interests,
class_name: "Support",
join_table: :users_supports,
foreign_key: :user_id,
association_foreign_key: :support_id
end
#app/models/support.rb
class Support < ActiveRecord::Base
has_and_belongs_to_many :users,
class_name: "Support",
join_table: :users_supports,
foreign_key: :support_id,
association_foreign_key: :user_id
end
#join table = users_supports (user_id, support_id)
by using this, you can populate the .interests or .users methods respectively:
#config/routes.rb
resources :supports do
post :interest #-> url.com/supports/:support_id/interest
end
#app/controllers/supports_controller.rb
class SupportsController < ApplicationController
def interest
#support = Support.find params[:support_id] # I need the post's id they are on
current_user.interests << #support
end
end
This will allow you to call #user.interests and bring back a collection of Support objects.
Okay, look.
What I suggested was an alternative to using interest column.
You seem to want to store a series of hashes for an associated model. This is exactly what many-to-many relationships are for.
The reason your data is being populated twice is because you're invoking it twice (u= is creating a record directly on the join model, and then you're inserting more data with <<).
I must add that in both instances, the correct behaviour is occurring; the join model is being populated, allowing you to call the associated objects.
What you're going for is something like this:
def interest_already_sent
support = Support.find params[:id]
current_user.interests << support
end
When using the method I recommended, get rid of the interest column.
You can call .interests through your join table.
When using the code above, it's telling Rails to insert the support object (IE support_id into the current_user (IE user_id) interests association (populated with the UserInterestSelf table).
This will basically then add a new record to this table with the user_id of current_user and the support_id of support.
EDIT
To store Hash into column, I suggest you to use "text" instead
def change
add_column :users, :interest, :text
end
and then set "serialize" to attribute
class User < ActiveRecord::Base
serialize :interest
end
once it's done, you can save hash object properly
def interest
#user = User.find(current_user.id ) # I need the logged in user's id
#support = Support.find(params[:id]) # I need the post's id they are on
u = FooBar.new
u.user_id = #user
u.support_id = #support
u.save # This saves a new Foo object..this is what I want
#user.interest = u.attributes # store hash
#user.save
end
To convert AR object to hash use object.attributes.
To store a custom hash in a model field you can use serialize or ActiveRecord::Store
You can also use to_json method as object.to_json
User.find(current_user.id ).to_json # gives a json string
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'm running a search using the Ransack gem.
Controller code (for ItemsController)
#q = Item.search(params[:q])
#search_results = #q.result
After running the search, #search_results contains multiple Items (Item 1, Item 2, Item 3, …). Where each Item is a hash:
Item = { "id" => "12", "name" => "shovel", "user_id" => "2", "type" => "A" }
In this case, for each Item hash, I want to add an additional key-value pair that translates the user_id into the name that is associated with that instance of the User model (i.e., User has_many :Items and Item belongs_to :Users)
I tried in the same controller code section
#search_results.each do |item|
item[:user_name] = User.find_by_id(item.user_id).name
end
But I get an error that I can't write to item. I suspect Ransack has something to do with it though, because if I just print #search_results, instead of getting the actual Item data, I get #<ActiveRecord::Relation::ActiveRecord_Relation_Item:0x000001025e6c78>
Help greatly appreciated!
Btw, in case there's even an easier way, the reason I want to add another key/value pair with the user_name is because the ultimate output is a JSON that is being picked up by another part of the codebase, and we don't want to run two separate database queries.
Firstly, you're receiving a ruby object back, which I take means you can create a set of attributes for in the model. Why don't you try using a getter in your item model to set for you:
#app/models/item.rb
Class Item < ActiveRecord::Base
belongs_to :user #-> needs to be singular
attr_accessor :user_name
def user_name
self.user.name
end
end
This means that instead of having to append a user_name attribute from your controller, every Item object will automatically have the user_name attribute anyway
A refactor of this would also be to use the delegate method inside your Item model:
Class Item < ActiveRecord::Base
belongs_to :user
delegate :name, to: :user, prefix: true #-> allows you to call #item.user_name
end
How can I save (insert) only associated objects without saving (updating) the base object?
For example I just want to save the phone numbers, I don't wan to resave/update the person object.
def create_numbers
#params => person_id => 41, person => {:phone_number => '12343445, 1234566, 234886'}
#person = params[:person_id]
nums = params[:person][:phone_numbers].split(',')
nums.each do |num|
#person.phone_numbers.build(:number => num)
end
#person.save #here I just want to save the numbers, I don't want to save the person. It has read only attributes
end
Models:
Person < ...
# id, name
belongs_to :school, :class_name => :facility
has_many :phone_numbers
end
PhoneNumber < ...
# id, number
belongs_to :person
end
This is a bit of a dumb example, but it illustrates what I'm trying to accomplish
How about #person.phone_numbers.create(:number => num)
The downside is that you wont know whether it failed or not - you can handle that, but it depends on how exactly you want to handle it.
The simplest approach is to replace your build(:number => num) with create(:number => num), which will build and save the phone_number object immediately (assuming it passes validation).
If you need to save them all after creating the whole set (for some reason), you could just do something like
#person.phone_numbers.each{|num| num.save}
I have a MailingList model that has_may :people
For most of my application, I only want to get people that are active
So #mailing_list.people should only return people that are active
In my model, I can't do
def people
self.people.find_all{ |p| !p.activated_at.nil? }
end
because that keeps calling itself. What is the ruby/rails way to automatically filter the people. Another possible issue is that I think self.people returns an array of active record objects where self.people.find_all... will return an array. This will cause some of my code to break. It's easy fixes but is there a way to return active record objects? It would be nice to have the option.
Thanks!
This is a perfect example for a named scope:
class Person < ActiveRecord::Base
named_scope :active, :conditions => 'activated_at is not null'
end
Then just call it:
# equivalent to Person.find(:all, :conditions => 'activated_at is not null')
#active_people = Person.active
You can also filter at the association level.
has_many :people, :conditions => {:activated => true}
You can used the standard find method or a dynamic finder. Your find might read as follows:
people.find(:all, :conditions => "activated_at = nil")
OR
people.find_all(:conditions => "activated_at = nil")
A dynamic version of this might read as:
people.find_by_activated_at(nil)