Rails passing params - ruby-on-rails

I feel like I'm missing a fundamentally easier way to do this; either way, I don't appear to have the syntax for array figured out. Trying to stuff things into the params array. Any help is appreciated.
#user = User.find(params[:user][:id])
array_of_match_information = Array.new
array_of_match_information[mentee] = #user.id
array_of_match_information[mentor] = self.id
array_of_match_information[status] = "Pending"
#match = Match.new(params[:array_of_match_information])
Thanks.

array_of_match_information = Hash.new
array_of_match_information[:mentee] = #user.id
array_of_match_information[:mentor] = self.id
array_of_match_information[:status] = "Pending"
EDIT
Hash is a key/value storage, like you intend to do.
mentee is a key that will be associated to a value #user_id
Array don't organize data (unless you consider the position in the Array is known and meaningful)
EDIT2:
And correct this:
#match = Match.new(array_of_match_information)
EDIT3:
I encourage you to have a look at http://railsforzombies.org, it seems you need a good tutorial.
Actually, building an app when you're learning could be hazardous because when you don't know basic architecture, you end up overcoding unmaintainable code.
For instance, your line:
array_of_match_information[:mentor] = self.id
seems really weird.

It seems, you're trying to implement a basic social network functionality. If I'm right, you should use associations. It would look something like this (I don't know the specifics of your mentor-mentee relation, so I suppose it's a many-to-many relationship):
class User < ActiveRecord::Base
has_many :matches
has_many :mentors, :through => :match
has_many :mentees, :through => :match
end
class Match < ActiveRecord::Base
belong_to :mentor, :class_name => 'User'
belong_to :mentee, :class_name => 'User'
end
Then, in your controller you could do this:
class matches_controller < ApplicationController
def create
# current_user is a Devise helper method
# which simply returns the current_user through sessions.
# You can do it yourself.
Match.create({ :mentee => #user, :mentor => current_user })
# "pending" status could be set up as a default value in your DB migration
end
end
But as I said, that's just a sample of code. I can't guarantee that it will work or suit your applications.
And you totally should check out this book

I'm not 100% sure what you're trying to do but at the very least, you should be using symbols when you set the 3 values:
array_of_match_information[:mentee] = #user.id
array_of_match_information[:mentor] = self.id
array_of_match_information[:status] = "Pending"
edit:
You should actually be doing this:
match_information = {}
match_information[:mentee] = #user.id
match_information[:mentor] = self.id
match_information[:status] = "Pending"
Without seeing your model, it's hard for me to know, but I suspect it actually wants the hash, not an array.

Related

rails associations :autosave doesn't seem to working as expected

I made a real basic github project here that demonstrates the issue. Basically, when I create a new comment, it is saved as expected; when I update an existing comment, it isn't saved. However, that isn't what the docs for :autosave => true say ... they say the opposite. Here's the code:
class Post < ActiveRecord::Base
has_many :comments,
:autosave => true,
:inverse_of => :post,
:dependent => :destroy
def comment=(val)
obj=comments.find_or_initialize_by(:posted_at=>Date.today)
obj.text=val
end
end
class Comment < ActiveRecord::Base
belongs_to :post, :inverse_of=>:comments
end
Now in the console, I test:
p=Post.create(:name=>'How to groom your unicorn')
p.comment="That's cool!"
p.save!
p.comments # returns value as expected. Now we try the update case ...
p.comment="But how to you polish the rainbow?"
p.save!
p.comments # oops ... it wasn't updated
Why not? What am I missing?
Note if you don't use "find_or_initialize", it works as ActiveRecord respects the association cache - otherwise it reloads the comments too often, throwing out the change. ie, this implementation works
def comment=(val)
obj=comments.detect {|obj| obj.posted_at==Date.today}
obj = comments.build(:posted_at=>Date.today) if(obj.nil?)
obj.text=val
end
But of course, I don't want to walk through the collection in memory if I could just do it with the database. Plus, it seems inconsistent that it works with new object but not an existing object.
Here is another option. You can explicitly add the record returned by find_or_initialize_by to the collection if it is not a new record.
def comment=(val)
obj=comments.find_or_initialize_by(:posted_at=>Date.today)
unless obj.new_record?
association(:comments).add_to_target(obj)
end
obj.text=val
end
I don't think you can make this work. When you use find_or_initialize_by it looks like the collection is not used - just the scoping. So you are getting back a different object.
If you change your method:
def comment=(val)
obj = comments.find_or_initialize_by(:posted_at => Date.today)
obj.text = val
puts "obj.object_id: #{obj.object_id} (#{obj.text})"
puts "comments[0].object_id: #{comments[0].object_id} (#{comments[0].text})"
obj.text
end
You'll see this:
p.comment="But how to you polish the rainbow?"
obj.object_id: 70287116773300 (But how to you polish the rainbow?)
comments[0].object_id: 70287100595240 (That's cool!)
So the comment from find_or_initialize_by is not in the collection, it outside of it. If you want this to work, I think you need to use detect and build as you have in the question:
def comment=(val)
obj = comments.detect {|c| c.posted_at == Date.today } || comments.build(:posted_at => Date.today)
obj.text = val
end
John Naegle is right. But you can still do what you want without using detect. Since you are updating only today's comment you can order the association by posted_date and simply access the first member of the comments collection to updated it. Rails will autosave for you from there:
class Post < ActiveRecord::Base
has_many :comments, ->{order "posted_at DESC"}, :autosave=>true, :inverse_of=>:post,:dependent=>:destroy
def comment=(val)
if comments.empty? || comments[0].posted_at != Date.today
comments.build(:posted_at=>Date.today, :text => val)
else
comments[0].text=val
end
end
end

Using a method within model, calling it from view

I have an Update model which belongs to users.
To show all of one user's friends' Updates, I am doing something like:
Update.where("user_id" => [array_of_friend_ids])
I know the "right" way of doing things is to create a method to create the above array. I started writing the method but it's only half-working. Currently I have this in my user model:
def self.findfriends(id)
#friendarray = []
#registered_friends = Friend.where("user_id" => id)
#registered_friends.each do |x|
#friendarray << x.friend_id
end
return #friendarray
end
I am doing the entire action in the view with:
<% #friendinsert = User.findfriends(current_user.id) %>
<% #friendarray = [] %>
<% #friendarray << #friendinsert %>
<%= #friendarray.flatten! %>
Then I'm calling Update.where("user_id" => #friendarray) which works. But obviously I'm doing things in a very hacky way here. I'm a bit confused as to when Rails can "see" certain variables from models and methods in the view. What's the best way to go about inserting an array of IDs to find their Updates, since I'm not supposed to use much logic in the view itself?
Mattharick is right about using associations. You should use associations for the question you mentioned in description of your question. If we come to the question at the title of your question;
let's say you have a User model.
These two methods are different:
def self.testing
puts "I'm testing"
end
and the other one is:
def testing
puts "I'm testing"
end
Pay attention to the self keyword. self keyword makes method a Class method. Which you can call it from your controllers or views like: User.testing.
But the one with out testing is a instance method. Which can be called like:
u = User.last
u.testing
Second one gives you possibility to use attributes of the 'instance' inside your model.
For example, you can show name of your instance in that method just like this?
def testing
puts "Look, I'm showing this instance's name which is: #{name}"
end
These are powerful stuff.
Practise on them.
Simple add another association to your project.
class User < ActiveRecord::Base
has_many :friendship
has_many :friends, :through => :friendship, :class_name => User, :foreign_key => :friend_id
has_many :friendship
has_many :users, :through => :friendship
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => User
end
I don't know if my synrax is correct, please try out.
Friendship has the attributes user_id and friend_id.
After that you should be able to do something like following to get the updates of a friend:
User.last.friends.last.updates
You can work with normal active record queries instead of hacky arrays..

Reassigning an ActiveRecord instance and corresponding foreign keys

In Rails/ActiveReocrd is there a way to replace one instance with another such that all the relations/foreign keys get resolved.
I could imagine something like this:
//setup
customer1 = Customer.find(1)
customer2 = Customer.find(2)
//this would be cool
customer1.replace_with(customer2)
supposing customer1 was badly configured and someone had gone and created customer2, not knowing about customer1 it would be nice to be able to quickly set everything to customer 2
So, also this would need to update any foreign keys as well
User belongs_to :customer
Website belongs_to :customer
then any Users/Websites with a foreign key customer_id = 1 would automatically get set to 2 by this 'replace_with' method
Does such a thing exist?
[I can imagine a hack involving Customer.reflect_on_all_associations(:has_many) etc]
Cheers,
J
Something like this could work, although there may be a more proper way:
Updated: Corrected a few errors in the associations example.
class MyModel < ActiveRecord::Base
...
# if needed, force logout / expire session in controller beforehand.
def replace_with (another_record)
# handles attributes and belongs_to associations
attribute_hash = another_record.attributes
attribute_hash.delete('id')
self.update_attributes!(attribute_hash)
### Begin association example, not complete.
# generic way of finding model constants
find_model_proc = Proc.new{ |x| x.to_s.singularize.camelize.constantize }
model_constant = find_model_proc.call(self.class.name)
# handle :has_one, :has_many associations
have_ones = model_constant.reflect_on_all_associations(:has_one).find_all{|i| !i.options.include?(:through)}
have_manys = model_constant.reflect_on_all_associations(:has_many).find_all{|i| !i.options.include?(:through)}
update_assoc_proc = Proc.new do |assoc, associated_record, id|
primary_key = assoc.primary_key_name.to_sym
attribs = associated_record.attributes
attribs[primary_key] = self.id
associated_record.update_attributes!(attribs)
end
have_ones.each do |assoc|
associated_record = self.send(assoc.name)
unless associated_record.nil?
update_assoc_proc.call(assoc, associated_record, self.id)
end
end
have_manys.each do |assoc|
associated_records = self.send(assoc.name)
associated_records.each do |associated_record|
update_assoc_proc.call(assoc, associated_record, self.id)
end
end
### End association example, not complete.
# and if desired..
# do not call :destroy if you have any associations set with :dependents => :destroy
another_record.destroy
end
...
end
I've included an example for how you could handle some associations, but overall this can become tricky.

How to check if a record exists before creating a new one in rails3?

Heres what I'm trying to accomplish:
I have a tagging system in place.
Tags are created, when Posts are created (posts has_many :tags, :through => :tag_joins.
A tag join is automatically created when a post is created with tags).
I want to check if the tag already exists. If it does I want to use the existing tag for the tag_join record, rather than creating a new tag record.
Here is my current code, which isn't working.
class Tag < ActiveRecord :: Base
belongs_to :user
belongs_to :tag_join
belongs_to :post
before_create :check_exists
def check_exists
tag = Tag.where(:name => self.name, :user_id => current_user.id)
if tag.nil?
tag = Tag.create(:name => self.name, :user_id => current_user.id)
end
end
end
This doesn't work though, I'm getting an error upon task creation...(the server is actually just timing out - I don't receive a specific error).
Any ideas?
Tokland said I was creating an infinite loop by telling it to create tag again - so I tried this:
def check_exists
tag = Tag.find_by_name_and_user_id(:name => self.name, :user_id => current_user.id)
if tag != nil
self.id = tag.id
end
end
And still get the server timeout
Edit: I'm not sure if this matters, but the way the tags are being added is similar to "http://railscasts.com/episodes/73-complex-forms-part-1
they're nested in the post form, and use something like this:
def tag_attributes=(tag_attributes)
tag_attributes.each do |attributes|
tags.build(attributes)
end
end
I'm wondering if this is stopping this whole thing from working? Also, using current_user.id in the model definitely seems to be an issue...
EDIT:
Something I have figured out:
this had to change, the format we were using before was incorrect syntax - generally used for a .where method.
def check_exists
#tag = Tag.find_by_name_and_user_id(self.name, self.user_id)
if #tag != nil
#return false
#self=#tag
end
end
The problem now is this, I can learn if it the tag already exists. But then what? If I go with the return false option, there is an error upon post creation, and the join record isn't created... The other option "self=#tag" obviously just doesn't work.
You're going to find it hard to to this from within the Tag model. It seems like what you want is to update the Post using nested attributes, like so:
post = Post.create
post.update_attributes(:tags_attributes=>{"0"=>{:name=>"fish",:user_id=>"37"}})
This is actually pretty simple to do by using a virtual attribute setter method:
class Post < AR::Base
has_many :tags
def tags_attributes=(hash)
hash.each do |sequence,tag_values|
tags << Tag.find_or_create_by_name_and_user_id(tag_values[:name],\
tag_values[:user_id])
end
end
> post = Post.create
> post.update_attributes(:tags_attributes=>{"0"=>{:name=>"fish",:user_id=>"37"}})
> Tag.count # => 1
# updating again does not add dups
> post.update_attributes(:tags_attributes=>{"0"=>{:name=>"fish",:user_id=>"37"}})
> Tag.count # => 1
There's a find_or_create_by_ function built right in to Rails
# No 'Summer' tag exists
Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
# Now the 'Summer' tag does exist
Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
http://api.rubyonrails.org/classes/ActiveRecord/Base.html (under Dynamic attribute-based finders)
You want to use the magic method find_or_create_by
def check_exists
tag = Tag.find_or_create_by_name_and_user_id(:name => self.name, :user_id => current_user.id)
end
Check out the ActiveRecord::Base docs for more info
The question I originally asked got pretty distorted by the end. So I'm separating it.
People who are trying to do what I originally asked can try this:
before_create :check_tag_exists
private
def check_tag_exists
#tag = Tag.find_by_name_and_user_id(self.name, self.user_id)
if #tag != nil
#
end
end
This will enable you to check if your record has already been created. Any further logic you can drop in that if statment.
I believe the other answers are a bit dated. Here's how you should probably accomplish this for Rails 4
tag = Tag.first_or_initialize(:name => self.name, :user_id => current_user.id)
if !tag.new_record?
tag.id = self.id
tag.save
end
try this
def check_exists
tag = Tag.where(:name => self.name, :user_id => current_user.id).first
tag = Tag.new({:name => self.name, :user_id => current_user.id}) unless tag
end
use Tag.new instead of Tag.create
where returns an empty ActiveRecord on finding no match.

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