views:

354

answers:

5

I'm sure this is a completely obvious beginner question, but trying to find answers to beginner Ruby questions on google is turning out to be an exercise in futility.

Anyway, suppose I have a database table that looks like this:

MyMessage
==================
int :id
string :from
string :to
string :messagetext

Now, if I need to expose a URL that takes in querystring params in the form of:

http://mysite.com/?sender=alice&receiver=bob&message=Hello

What is the best way to map the querystring params to my model?

Right now, I'm just doing it by brute force inside the controller:

@sender = params[:sender]
@receiver = params[:receiver]
@message = params[:message]

@mymessage = MyMessage.new
@mymessage.from = @sender
@mymessage.to = @receiver
@mymessage.messagetext = @message

@mymessage.save

My guess is that I should be able to create a class representing this type of querystring request, i.e.

class Request
  attr_accessor :from
  attr_accessor :to
  attr_accessor :message
end

Then use some RoR magic to create the Request object from the params and then more magic to create a method that maps the Request object to the MyMessage object.

Any suggestions? Does this make sense at all?

A: 

If you're using active record then the method you're looking for is update_attributes.

It'll look something like this:

def update
    @model = Model.find(params[:id])
    @model.update_attributes(params[:model])
end

You need to be aware that this'll update anything the user passes in, including fields you might not want to change, for example a flag like is_admin on your user model.

I suggest applying attr_accessible to any properties you want a user to be able to edit, for example:

class Model < AR:Base
    attr_accessible :my_safe_field_name
end
jonnii
That makes sense to me if the fields being passed in are a one-to-one mapping of the model, but in this case they aren't (and can't be, since we want the model to be generic and then expose various RESTful URLs for 3rd parties to consume). So, the Model might have 'to', but one vendor might pass it is as 'receiver' and another one might use 'sentTo', for example. I think I need to make custom classes for each implementation and then 'map' them to the generic class, but I'm not sure how to go about doing so. Does that make sense?
jerhinesmith
Well params[:model] is just a hash, so you could always write a function to change the keys to match your model. However, why can't you just have one representation for both?
jonnii
A: 

watch this railscast. it explains a little about mass assigment and what you need to do to make it secure.

Chris Drappier
+1  A: 

You could do something like:

class MessageRequest
  def initialize(params = {})
    @sender = params[:sender]
    @receiver = params[:receiver]
    @message = params[:message]
  end

  def apply_to_my_message(my_message = MyMessage.new)
    my_message.from = @sender
    my_message.to = @receiver
    my_message.message_text = @message
    return my_message
  end
end

Then your controller code becomes:

@mymessage = MessageRequest.new(params).apply_to_my_message

If you just want to customise it for different named parameters from different places you could have protected methods returning the parameter name for each of the attributes. E.g.

class MessageRequest
  def initialize(params = {})
    @sender = params[sender_param]
    @receiver = params[receiver_param]
    @message = params[message_param]
  end

  ...

  protected

  def sender_param; :sender; end
  def receiver_param; :receiver; end
  def message_param; :message; end
end

You can then create subclasses for different named parameters like:

class OtherMessageRequest < MessageRequest

  protected
    def receiver_param; :sentTo; end

end

Probably overkill for what you've described but hopefully what you're getting at.

Shadwell
Voted up. Once I digest it a bit more, if it looks like it works, I'll accept it. Thanks!
jerhinesmith
+2  A: 

You could do something like this:

class MyMessage < ActiveRecord::Base
  def self.new_from_web(params)
    returning(self.new) do |message|
      message.from        = params[:sender]
      message.to          = params[:receiver]
      message.messagetext = params[:message]
      message.save
    end
  end
end

class MessageController < ApplicationController
  def save_message
    MyMessage.new_from_web(params)
  end
end

Of course, this whole thing could likely be made simpler and more conventional by using more explicitly RESTful semantics.

Yehuda Katz
I like this solution -- it has a very clean feel to it. However, what do you mean by more "explicitly RESTful semantics"? Is it possible to give a short example?
jerhinesmith
A: 

Simple usually goes a long way:

@message = MyMessage.new(params.slice(:sender, :receiver, :message))
Tor Erik Linnerud