Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
plasticbugs
Dec 13, 2006

Special Batman and Robin
I have an EXTREMELY rudimentary question.

I've set up two models: Games and Consoles
Console has many games
Game belongs to Console

I'm creating New games and assigning consoles to them. How is it that I can say:
game.console.title and get back the title of the console assigned to the game. However, I can't say "console.game" and get back all the games that belong to that particular console.

The Games table is getting the console_id, but the Console table isn't filling up with multiple game_ids as they're being assigned to consoles.

What fundamental ActiveRecord concept am I missing here? Should I be inserting game_ids into the console table at the same time that the console_id is being inserted into the game table? How do I ultimately list all games for a particular console if the console model isn't storing game_id?

EDIT: This is just an exercise as I'm wrapping my head around rails. I won't be putting anything of this nature onto the web.

Here's a pic of my ugly interface so you can see what my New Game action looks like

plasticbugs fucked around with this message at 10:19 on Oct 11, 2009

Adbot
ADBOT LOVES YOU

plasticbugs
Dec 13, 2006

Special Batman and Robin

Sewer Adventure posted:

console.games will return an array of games

Nope, I'm still getting back an empty array for each console.

Here's my ruby code for assigning a console to a game using a drop-down list:
code:
<%= collection_select(:game, :gameconsole_id, @gameconsoles, :id, :title) %>
And here are my two models:
code:
class Game < ActiveRecord::Base
  belongs_to :gameconsole
end

class Gameconsole < ActiveRecord::Base
  has_many :games
end
And it's working on a per-game basis. I'm able to assign each game a gameconsole. But, when I list each console, I'd like to get back a list of games for that console. Is the only way to do that is by doing a find on @games where gameconsole_id == X?

I thought ActiveRecord provided a way to go through @gameconsole to pull each console's games. Is that an incorrrect assumption?

I think I'm missing an important concept here. I assumed that ActiveRecord automatically handled this without a join table. Aren't join tables mainly for HABTM relationships?

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

Nope, no need, that's 'has many and belongs to'. AR is smart enough to do backwards association without having to have foreign keys in both directions. When you say has_many :games, and then you say
code:
z = Console.first
z.games
Returns an array of Game objects by the sql query

code:
SELECT * FROM games WHERE console_id = 1
It took me a while to think of relationships the Rails way. It's easy to understand but sometimes I would get confused as to what to say 'has' and 'belongs to'. I saved this picture just in case it goes away as a reference:

http://mboffin.com/stuff/ruby-on-rails-data-relationships.png

e: looking at the classes above I think you're hitting issues with naming conventions. class Gameconsole will look for a table called gameconsoles, what I think you want is class GameConsole, which will look for a table called game_consoles. What's your migration? Did you use script/generate? I highly recommend using script/generate because it'll auto-name everything correctly. Even if that isn't the issue it gets you used to naming conventions to make everything highly readable and consistent.

That helps a lot. Thanks. I think my bigger issue was with using my app's "script/console" to update each record and it wasn't showing the state changes on my other model's instances. When I actually got to coding my views, the pages displayed as they should with each console spitting out their associated games and each game knowing which gaming console it belonged to.

Thanks again, I'm saving that chart. It will be very handy.

plasticbugs
Dec 13, 2006

Special Batman and Robin

Molten Llama posted:

If you're not explicitly calling save after making a change, there has been no change as far as the app's concerned. It's pulling from the DB, and without a save, the DB is untouched.

You're right. I wasn't calling save on the right instance. Seems to be working as advertised, now. Thanks.

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

Another thing, anything and everything, whenever it writes back to the database, no matter if it's automatically saved like .create or explicitly like .save always always always goes through validations unless you really tell it to save_without_validation. Which reminds me, your Game class really should look like:

code:
class Game < ActiveRecord::Base
  belongs_to :game_console
  validates_presence_of :game_console_id
  validates_numericality_of :game_console_id
end

This is great advice, too. I'm seeing the importance of validations and well-written error messages as I get deeper.

Pardot posted:

The other non-obvious, big thing you need to get in the habit of is to use add_index in your migrations on at least your foreign keys. Documentation.

I'll start making it a point to add indexes on all my foreign keys. I'd like to think I'm working towards "best practices", so thanks for the advice.

plasticbugs
Dec 13, 2006

Special Batman and Robin
Can someone please help me understand why my code is so unwieldy? I've been teaching myself, but it looks like I'm missing some remedial Ruby concepts.

I'm writing a browser based game/following a tutorial as another learning exercise and have a simple combat system set up. My fight(monster) method returns an Array called @combat.

I'm trying to customize my model so the browser receives different messages depending on some of the equations inside the fight(monster) method.

It works, but what I've done can't possibly be the best way to go about spitting out messages from a method that returns an array that 'should' be able to store a bunch of additional strings for me.

Here's some of the code in my Player model along with questions (as comments):

code:
# The first method returns the first message I want to convey if the player is struck first by a monster.
# The sentence cuts short because the View returns the name of the monster. Shouldn't this 'message'
# method return the monster's name instead of having the view do it? And how would I go about doing
# that? I'd need to make an instance of Monster (which is stored in the session) available to this message
# method which exists inside a different class.
 
  def message
    if @firstAttack > 95
      "You were surprised by a "
    end
  end  
  
# What if I wanted to also say in another message something like "You got lucky and hit for XXX damage".
# What's the best way to send that message to the browser without writing what seems like another
# one-time-use method such as "message2" based on another instance variable pulled from the
# fight(monster) method. Like, should I be declaring 'damage' as another instance variable and returning a
# separate string like "You got lucky" by checking the value of 'damage inside another method? That seems 
# very wrong to me.

# Here's the fight(monster) method just for completeness.

   def fight(monster)
     combat = Array.new

# This gives the player a 95 percent chance of attacking first
      @firstAttack = 1 + rand(100)

     if @firstAttack < 95
       turn_number = 0
     else
       turn_number = 1
     end

     while (self.alive? and monster.alive?)
       turn_number += 1

       # switch the roles of attacker and defender back and forth

       if (turn_number % 2 != 0)
         attacker = monster
         defender = self
       else
         attacker = self
         defender = monster
       end
       
       y = 1 + rand(5)
       x = 1 + rand(10)/y

        damage = attacker.attack - defender.defense + x
        if damage < 1
          damage = attacker.attack
        end
        
        if damage > defender.cur_hp
          damage = defender.cur_hp
        end

         defender.cur_hp -= damage

         # We'll only make a turn record for those cases where something actually happens
         turn = Hash.new
         turn["attacker"] = attacker.name
         turn["defender"] = defender.name
         turn["damage"] = damage

         #save this turn in the combat transcript
         combat << turn
     end
     
     if (self.alive?)
         # You lived and earned gamer points
         self.gamer_points += monster.gamer_points
       else
         # There are no penalties for losing at the moment
         
         end

       # We only save the user's record back to the database. The monster needs to stay
       # unchanged in the database no matter how much damage we did to it so the next one
       # we encounter will still be pristine

       self.save

       # Return the results of combat
       combat
     end
  

plasticbugs fucked around with this message at 11:03 on Dec 2, 2009

plasticbugs
Dec 13, 2006

Special Batman and Robin

Pardot posted:

I'm not happy with the perform_turn, but here's a shot.

code:
class combat(player, monster)
  attr_accessor :turns
  
  def initialize
    @player  = player
    @monster = monster
    @turns = []
    fight!
  end
 ...
Have you taken a look at rspec? The whole BDD process of write test, do the smallest thing to get it to pass, write another test, would likely guide you to something like this. Though I didn't test drive this refactoring, I'm afraid.

This is an enormous help. Thank you for your time. I'm taking your advice and will be looking into Rspec and Cucumber this weekend. Is Cucumber the best way to go about BDD? I know enough Ruby/Rails to be exceedingly dangerous, so is it too early for me to start learning how to write tests? Or is writing tests an integral part of learning (not just Rails but intelligent programming)?

EDIT: typo

plasticbugs fucked around with this message at 21:39 on Dec 2, 2009

plasticbugs
Dec 13, 2006

Special Batman and Robin

Operation Atlas posted:

It is never too early to start writing tests. Learning rails without testing is kind of a guessing game and what works seems to be magic or an incomprehensible collection of mystery settings. Using rspec (or another testing framework) will take the guesswork out and will let you know exactly what broke when so that you can fix things more quickly, more easily, and with less magic.

Thanks. One more question. Any advice on first steps? I see that Pragprog has an upcoming book on the subject. Until it comes out, is there another book you recommend?

plasticbugs
Dec 13, 2006

Special Batman and Robin

phazer posted:

Assuming you mean The rSpec Book the beta PDF is a prefectly fine start. I went from 0 knowledge of cucumber/rspec to regularly doing BDD & testing in every project.

Yep, I bought it last week and I'm amazed at how much I've been able to grasp so far. The PDF will live on my Sony Reader until the paper version comes out.

I have a question about IDEs and design. Is there a way I can set-up Textmate to show a live preview of my edits in a browser window - like if I'm tweaking CSS? If not, is this something that Coda does with a Rails project?

plasticbugs
Dec 13, 2006

Special Batman and Robin
I stupidly upgraded my Ruby install on OS X Snow Leopard to 1.9.1 while following a new Rails book tutorial. To my credit, I followed the directions word-for-word, thinking what a good idea it would be to have the latest version of Rails AND the latest stable version of Ruby, too.

Now WEBrick crashes like a champ (EDIT: mongrel fails to build, too!) - obviously Rails isn't ready for Ruby 1.9.1. Is there an easy way to downgrade my Ruby back to 1.8.7?

EDIT 3: Fixed my code and WEBrick is still crashing.

My Ruby is in:
/usr/local/bin/ruby

Also, if you are gracious enough to help me, please assume that I'm a complete idiot.

EDIT 4: Holy poo poo. After an hour of wrestling with installing and uninstalling Ruby, Gems and Rails, my ultimate solution was to TRASH my Ruby binary, my Rails binaries (rake, heroku et al.) AND my entire Gem folder (my 1.8 AND my 1.9 gems) - every single Gem. I reinstalled Ruby 1.8.7, reinstalled RubyGems and all my gems including Rails.

It works. Lesson learned. Don't do what I did.

plasticbugs fucked around with this message at 09:44 on Feb 3, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

Use rvm. I've come to love it like a gay go-to bottom that's always willing. It works even without a system Ruby.

Halfway through my process I found rvm, but I was well past the point of no return. I didn't realize the amount of things that were about to break as I "sudo rm'd" Rails binaries one-by-one from /usr/local.

I will begin using rvm to avoid this nonsense with future updates.

plasticbugs
Dec 13, 2006

Special Batman and Robin

LordNova posted:

Anyone else going to the LA Ruby conference this weekend?
http://www.larubyconf.com/

drat, I wish I saw this sooner - spent a day by myself not talking to anyone.

I went for the class day on Friday, but not for the Saturday talks. Did you end up going?

I took the Nokogiri/Mechanize class and the Rubygames/Gamebox class. Both were well worth the extremely cheap $60 per course fee. The guy who created Nokogiri gave a nice primer on Xpath and CSS selectors which was a great foundation to learn from.

plasticbugs
Dec 13, 2006

Special Batman and Robin
I've got another very rudimentary Ruby question. I'm attempting to access data inside an XML file with Nokogiri. The data is coming back as a Hash with a nested Array. Then, inside that Array is a Hash with another Array nested inside it. Inside THAT Array is a Hash with the data I want to access.

Given:
code:
{a => [{b =>[{c => "value"}]}]}
What's the best programmatic way to access "value" in one line of code? The way I've been doing it can't possibly be right.

plasticbugs
Dec 13, 2006

Special Batman and Robin
I should clarify, I wasn't using Nokogiri. I was using a gem for a specific API, which I'm guessing has its quirks.

Flobbster posted:

Does this work?
code:
thing['a'].first['b'].first['c'] # assuming a, b, c are strings and not symbols, in which case:
thing[:a].first[:b].first[:c]
I'm assuming you will know a priori that those are single-element arrays that you're dealing with. I used first instead of [0] so the whole thing didn't become a huge square bracket mess, even though it's a little longer.

I don't think any of that needs to be parenthesized to work, but I'm not in front of my interpreter right now.

Yes, sorry. a, b, and c are strings. EDIT: That's the answer I was looking for. Thanks!

EDIT: Deleted stupid question. Learning all this on my own with nothing but some books and Google is challenging, but this thread has been such a huge help. Thanks.

plasticbugs fucked around with this message at 09:15 on Mar 12, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin
I'm confused again. Another dumb question:

I'm working with a Nokogiri object and am attempting to populate a page with TV episode titles and links to those titles with data from an XML file.

This gets all the links:
code:
 
doc.css('episode link').each do |link|
     link.text
end
This gets all the episode titles
code:
doc.css('episode title').each do |title| %>
     title.text
end
It seems the best solution was to create an array and populate it with hashes like so:
code:
[{:link=>"/Buffy_The_Vampire_Slayer/episodes/329033", :title=>"Unaired Pilot"},
{:link=>"/Buffy_The_Vampire_Slayer/episodes/28077", :title=>"Welcome to the Hellmouth (1)"}, 
{:link=>"/Buffy_The_Vampire_Slayer/episodes/28078", :title=>"The Harvest (2)"}, 
{:link=>"/Buffy_The_Vampire_Slayer/episodes/28079", :title=>"The Witch"},
etc...

This was my approach to putting the results of both operations into the array above, and it worked, but it seems VERY sloppy:
code:
@myarray = []
@doc.css('episode link').each do |link|
@myarray << Hash[:link, link.text]
end

i = 0
@doc.css('episode title').each do |title|
@myarray[i][:title] = title.text
i = i + 1
end
1. I'm not sure where to put that code, now. It's too much logic to stick in my view, but when I copy/paste it into my Controller's "show" action, it takes 12 seconds to render the view. Which is NOT right at all.

2. Is there a better way to put those results into a structure that I can iterate over in my view to create a list of Episode titles and links? I feel like I'm missing the obvious, easy solution to displaying this data in my view.

plasticbugs fucked around with this message at 09:28 on Mar 12, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin
I'm checking in to say that I've solved my own problem from a few posts up with regards to creating a hash in one step from an XML file using Nokogiri. For anyone getting started with Ruby or Nokogiri, this may be of some help.

Here's my refactored code:
code:
   @episodes = []
    doc.css('episode').each do |episode|
      @episodes << Hash[:title, episode.css('title').text, :link, episode.css('link').text]
    end
This saves me the extra steps of adding the titles and links to my array using separate operations. It also lets me add some logic in there to check whether a certain title even HAS a link.

plasticbugs
Dec 13, 2006

Special Batman and Robin

Pardot posted:

You should use Enumerable#map
code:
   @episodes = doc.css('episode').map |episode|
      { :title => episode.css('title').text, 
        :link => episode.css('link').text }
    end

Thanks for this! I'm reading up on it right now.

plasticbugs
Dec 13, 2006

Special Batman and Robin
EDIT: I'm an idiot.

I had a column in my model named "Type".

plasticbugs fucked around with this message at 04:15 on Aug 23, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin

Nolgthorn posted:

I've done this or similar a lot with Rails.

One time it was a little obscure one and a problem didn't crop up with it until weeks later with some weird insane error on one specific specialized action. Now I use a thesaurus to pick most of my names.


A couple of current favourites are:

Crowd, Stream, Disclosure, Party, Participant, Actor, Candidate, Rival.

I'm going to start using your thesaurus trick. This wasn't my first run-in with a 'protected' word.

plasticbugs
Dec 13, 2006

Special Batman and Robin
I have another very rudimentary question. I recently reworked my app in progress which until recently had three separate models called "Game", "Movie", and "Product" - but they all basically had the same controller structure and had nearly identical views and model definitions.

To prevent this repetition, I have created a model called "Thing" and I subclassed it to create "Game", "Movie", and "Product" models.

So my model definition for "Game" looks like this:
code:
class Game < Thing

  def mymethod(something)
    something
  end

end
Now that I've done that, I need a way to separate Games from Movies and Products.

Right now if I do:

code:
Game.all
Rails returns EVERY record in the Things table - which is obviously not what I want. There must be an easy way to return just a list of Games, but ideally without adding a separate column to declare a Thing type that I have to check for. What am I missing?

EDIT: Would I be better off going back to my 3 separate model solution, each having their own table? Meaning, I would not have a very DRY app, but at least I could rely on Rails conventions without much extra hassle.

plasticbugs fucked around with this message at 20:36 on Oct 20, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

Yes. Don't do what you did. It's clever, but unnecessary. Sometimes DRY goes too far. Also, subclassing doesn't do what you think it does.

If your models are overlapping, then you need to normalize the database anyway.

It looks like a little repetition will likely save me from myself and the quirks of STI. Thanks for your help!

I'll take a look at how I've structured my database to ensure I'm not overly complicating it. Having "Games", "Movies" and "Products" in separate tables seems like it may speed up queries, and will at the very least simplify my queries.

plasticbugs
Dec 13, 2006

Special Batman and Robin
I need a login system for my simple CMS site. Instead of rolling my own, I'd like to use an existing library/gem.

Between Authlogic, Devise and Omniauth, which do you guys recommend for an absolute beginner? I was leaning towards Devise. I wanted to avoid rolling my own simply because I think I'd actually learn more by working with modules and figuring out how to integrate them into an existing project (something I haven't quite done yet).

Edit: Holy poo poo. Devise is dumb simple to use and configure. I think I have my answer.

plasticbugs fucked around with this message at 09:32 on Oct 22, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin
Please save me from myself. I'm interacting with the Amazon Product Advertising API. One of the item attributes kicks out a verbose, formatted product description.

However, it prints to the browser as a bunch of escaped html entities, like so:
________________
&lt;i&gt;New Super Mario Bros. Wii.&lt;/i&gt; Supporting 2-4 players in side-scrolling co-op and competitive platforming action, and featuring a mix of fan favorites and new characters, new powerups and various input options via the Wii Remote, it is destined to become an instant classic in one of the most beloved game franchises of all-time. &lt;style type="text/css"&gt; .caption { font-family: Verdana, Helvetica neue, Arial, serif; font-size: 10px; font-weight: bold; font-style: italic; } ul.indent { list-style: inside disc; text-indent: -15px; }
________________

I'm sure there's a built in Rails method that cleans this up, and prints to the browser as parsed HTML. I just don't know what it is.

EDIT: I figured it out:

require 'cgi'
CGI.unescapeHTML()

EDIT EDIT: For anyone that cares:

Once you've unescaped the HTML entities, you still need to format your string with the html_safe method. Then, Rails will actually print the formatted HTML to the view.

plasticbugs fucked around with this message at 06:00 on Nov 2, 2010

plasticbugs
Dec 13, 2006

Special Batman and Robin
I'm writing a simple app that allows you to input a text message and you get back a random code number so you can retrieve the text message later.

I have a Message model with two columns:
contents
codeNumber

I would like the app to generate a unique codeNumber and save it to the database along with the message contents. If I go into the console and do

code:
m = Message.new
m.codeNumber
a random code gets spit out on the command line, BUT it doesn't add the code to the instance's codeNumber.

If I do:

code:
m.codeNumber = m.codeNumber
it works, but obviously that's a terrible work-around.

What am I doing wrong?


My Message model:

__________
class Message < ActiveRecord::Base

def codeNumber
generateCode
end

private
def generateCode
Code Logic in here
end

end
____________


EDIT: I may have found a solution, but it may not be elegant. Please look below.

I revised my Model to institute a callback. This still seems wrong, but it works.

code:
class Message < ActiveRecord::Base
  before_create :codeNumber
    
  def codeNumber
    self.codeNumber = generateCode  
  end
    
private
  def generateCode
    CODE LOGIC HERE
  end
  
end
Is there a better way to do this?

plasticbugs fucked around with this message at 08:14 on Mar 30, 2011

plasticbugs
Dec 13, 2006

Special Batman and Robin

Pardot posted:

Yeah, you're on the right track, but this is probably what closer to what you wan

Thanks for your help! I'm reading up on memoization right now.

plasticbugs
Dec 13, 2006

Special Batman and Robin
I have some routing weirdness going on with my 2-page site. I'm not sure if it's my routes or my controller that is causing the problem.

Here is my controller action:

code:
def create
    @message = Message.new(params[:message])
        
    if @message.save
      flash[:notice] = 'Your message was created!'
      redirect_to :action => "show", :pick_up_code => @message.pick_up_code
    else
      render :action => "new"
    end
 end
and my entire routes file:
code:
Txtapp::Application.routes.draw do
  resources :messages, :only => [:new, :create]
  match '/:pick_up_code' => 'messages#show'
  root :to => "messages#new"
end
Right now, if validation fails on CREATE, the new action is rendered, but the URL in the address bar is
http://mysite.com/messages. And, that doesn't even go anywhere. What's causing that address to pop in there? I'm assuming it's REST, but I don't know how to avoid that and keep the benefits that REST is providing (simplicity).

If validation fails, is there a way to say render the new action as the root URL? If I use 'redirect_to :root' instead of 'render', I'm not able pull the validation errors into the page.

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

code:
resources :name, :only
Can cause problems if you use Rails' automatic builders like you seem to do, since the builders assume all of the resource routes are available. Just do resources :name. If you don't want certain actions but don't understand where they are coming from, do something like

code:
def update
  raise NotImplementedError
end

def delete
  raise NotImplementedError
end
There when you're playing with your site (more like running tests) and following the generated forms you'll hit errors that you can debug and fix instead of the routes being wonky.

I'll try this right now and will report back. Thanks!

EDIT: I ended up leaving the route in there with ":only" but went on to add a route to match the RESTful aberrant route generated by my call to 'render' after a validation fails:
match '/messages' => 'messages#new'

I don't mind this workaround and it keeps my app from breaking. Here's the site by the way: http://www.3dstxt.com

It's basically a messaging site crossed with bit.ly. It's made for users of the Nintendo 3DS, which only supports 16 character messaging. It's one of my first Rails projects.

Thanks for your help!

plasticbugs fucked around with this message at 08:53 on Apr 4, 2011

plasticbugs
Dec 13, 2006

Special Batman and Robin
The first Rails site that I've ever put into production is now live at 3dstxt.com. However, my question may not be Rails specific.

Is there an easy Rails Way to prevent Google from indexing certain pages? I'd rather Google didn't crawl my app's "show" pages, where users are posting semi-personal content (email addresses, 3DS Friend Codes, etc).

EDIT for clarity: Because of how the URLs are created, this includes any URL that matches 3dstxt.com/XXXXX

I still would like Google to index the root at 3dstxt.com/ and possibly blog pages or help pages that may eventually exist.

Is robots.txt the only option?

plasticbugs fucked around with this message at 20:47 on Apr 6, 2011

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

robots.txt in the public directory would be your best bet. Easy, simple, and all the search engines worth a drat respect the directives in it.

By the way, if you did the project as a way to learn Rails, great, but if you wanted something simpler but like the Ruby language Sinatra would likely have been a better fit for a one controller application.

I uploaded a robots.txt just now. Thanks for the help! I started seeing some people sharing their email addresses and Nintendo Friend Codes, and I didn't want Google indexing that stuff. It'd be nice to have some extra free search traffic, but not at the expense of someone's privacy. Hopefully Google will still index my site's root.

As for Sinatra, I considered it - I had built a simple Twitter app using Sinatra and an online tutorial. However, I'm still wrapping my head around VERY basic concepts and tools - git, sessions, routes - so I stuck with Rails.

plasticbugs
Dec 13, 2006

Special Batman and Robin

Cock Democracy posted:

If you don't want google to index these pages, consider whether you really want unauthenticated users to see these pages at all.

These pages are intended to be viewed by total strangers (other Nintendo 3DS owners) who receive a URL that's being broadcasted from the user's Nintendo 3DS. So I can't restrict pages based on authentication.

plasticbugs
Dec 13, 2006

Special Batman and Robin
My validations are causing some strange goings on with my view.

The site basically lets the user create a message and gives the user a randomly generated message_url for sharing that message - like bitly. Users with accounts can create messages and choose a custom message_url like example.com/myurl.

If a user creates a message - but chooses a custom URL that is already taken, the custom url fails validation.

The way I've built my callbacks, the system spits out a randomly generated url if it finds the chosen custom url is already being used.

In a nutshell:
1. if message_url is blank or nil, a randomly generated url is created and the record is saved as a valid message.
2. If message_url is not blank and passes validation, the record is saved as a valid message with a custom url
3. If message_url is not blank and fails validation, the record is NOT saved and the view is re-rendered with a randomly generated message_url.

The problem is, after the validation fails, the current view renders with a suggested url, but it also presents the very message which failed validation at the bottom of the list of the user's messages. The record is not valid and the its link goes nowhere. Why is this happening?

Thanks in advance to the generous Rails users in this thread who continue to offer priceless help and advice.

Here is a picture:


This is my User controller's show action:
code:
 
 def show
    @user = User.find(params[:id])
    @message = Message.new
    @messages = @user.messages
  end
 
My view:
code:
<% if @messages.count > 0 %>
<div class="message_list">
	<h1>Saved StreetPass Messages</h1>
<% @messages.each do |message|%>
#Spits out each message belonging to the user here
Here is my Message model
code:
class Message < ActiveRecord::Base

  validates :message_url, :uniqueness => {:case_sensitive => false}

  validates_length_of :message_url,
                                      :minimum => 3,
                                      :maximum => 5, :if => :message_url
  
validates_length_of :contents,
                      :minimum => 1,
                      :maximum => 5000
  
  before_validation(:if_blank)
  after_validation(:create_message_url_until_valid) 
  
  before_save(:downcase_message_url)

private

  def if_blank
     if self.message_url.blank?
       self.message_url = nil
     end
  end

  def downcase_message_url
    self.message_url = self.message_url.downcase
  end

  def create_message_url_until_valid
    charset = %w{ 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z }
    self.message_url ||= (0...5).map{ charset.to_a[rand(charset.size)] }.join
    
    while Message.find_by_message_url(self.message_url.downcase)
      self.message_url = (0...5).map{ charset.to_a[rand(charset.size)] }.join
    end
  end
end

plasticbugs
Dec 13, 2006

Special Batman and Robin

Anveo posted:

Not sure if the 'random-ness' is a feature but if that doesn't matter just do a base conversion on the primary key:

code:
# to get shortcode
def shortcode
  id.to_s(36)
end

# to find
msg = Message.find params[:msg_id].to_i(36)

UPDATE: Regarding my problem above. I took the cowards way out. In order to prevent the invalid record from showing up in my view, I'm checking to see if there are errors on any of the messages. If there are, the view won't render them. Problem solved, but still not happy that I couldn't find a more elegant solution.

Thanks again for the help Anveo and Obsurveyor.


Thanks for the tips. I did want to obfuscate the Message ID somehow and I probably will soon to avoid the expensive lookup by message_url operation.

Also, I believe I left out some crucial info that may point to why my view is showing an invalid object.
The Create action from my Messages controller, which duplicates a BUNCH of stuff in the Users Controller Show action (from my previous post), but if I remove any of the lines in either controller, Rails complains of nil objects.

code:
  def create
    
    if signed_in?
      @user = current_user
      @message = current_user.messages.build(params[:message])
      @messages = current_user.messages
    else
      @message = Message.new(params[:message])
    end
        
    if @message.save
      flash[:notice] = 'Your message was created!'
      redirect_to :action => "show", :message_url => @message.message_url
    elsif signed_in?
      render :action => 'users/show'
etc. etc.

plasticbugs fucked around with this message at 09:21 on Apr 18, 2011

plasticbugs
Dec 13, 2006

Special Batman and Robin

BonzoESC posted:

If it's indexed in the database it shouldn't be expensive.

If you still want to have something obfuscated but that doesn't look like you're just incrementing, can I recommend https://github.com/bkerley/have-code ? It probably works just fine with ActiveRecord 3.

Thanks for this. I'll look into it.
One more question popped up. I want to limit users to creating 5 total items. Items belong to Users.
Can I run a validation inside my Item model that won't allow a user to create a sixth item by checking self.user.items.count < 6

In pseudo-ish code (obviously, this doesn't work)

class Item
validates_numericality_of self.user.items.count, :less_than => 6
end

plasticbugs
Dec 13, 2006

Special Batman and Robin

NotShadowStar posted:

I don't think a whole inner class would be necessary for just one validation. You could do it with

code:
class Message
  validates :must_have_less_than_six_items

  private
  def must_have_less_than_six_items
    errors.add "Cannot have more than five items" if items.count > 5
  end
end
Inner classes for validations are better used when including a module that defines a bunch of factory validations. It's how Rails handles its own validators.

I did exactly this. Thanks to both of you.

plasticbugs
Dec 13, 2006

Special Batman and Robin
I made the bad decision to begin my Rails app with Disqus comments. I figured it would be easy to export them when I outgrew the simple stop-gap solution it provided.

I know how it's organized, but I'm having trouble figuring out a simple way of parsing all the entries.

The XML file is basically set up like this:

pre:
<thread dsq:id="12345">
  <id>foo</id>
 </thread>
where "foo" is a unique entry in my Post model's "code" column (and appears in the Post URL, so that Disqus knows to display the right comments on the right post)

then, much further down the same XML file

pre:
<post id="177002220">
		<id/>
		<message><![CDATA[This is my awesome comment!]]></message>
		<createdAt>2011-04-02T19:02:04Z</createdAt>
		<author>
			<email>me@example.com</email>
			<name>myname</name>
			<isAnonymous/>
		</author>
		<ipAddress>192.168.1.1</ipAddress>
		<thread dsq:id="12345"/>
</post>
I basically need to parse through all the "threads", and for each unique thread ID, I need to search across all the "posts" in the XML to find all the entries that share that "thread id". Then, save out a Comment for the matching data.

This is what I have, but it just hangs. Am I even close?

pre:
require 'nokogiri'

@doc = Nokogiri::XML(File.open("~/Downloads/disqus.xml"))

@doc.css("thread").each do |t|
    @t = t
    @thread_id = t.attributes["id"].value
      @doc.css("post").each do |p|
        @p = p
        @post_id = p.attributes["id"].value
      end
  
    if @thread_id == @post_id
      c = Comment.new
      c.code = @t.css("id").text
      c.author = @p.css("author name").text
      c.contents = @p.css("message").text
      c.save!
    end
end

plasticbugs fucked around with this message at 06:08 on Feb 12, 2012

plasticbugs
Dec 13, 2006

Special Batman and Robin

UxP posted:

There isn't much of a reason to separate out the threads and comments loops, just go for it all in one shot. I've never used css selectors for XML node traversal, so I'd honestly just go for basic XPath.

pre:
require 'nokogiri'

doc = Nokogiri::XML(File.open(File.join(Dir.home(), "Downloads", "disqus.xml")))

doc.xpath('/xmlns:disqus/xmlns:thread').each do |t|
  doc.xpath(%Q<//xmlns:post[xmlns:thread[@dsq:id="#{t.attr('id')}"]]>).each do |c|
    comment = Comment.new
    comment.code = t.attr('id')
    comment.author = c.xpath('xmlns:author/xmlns:name').inner_text()
    comment.message = c.xpath('xmlns:message').inner_text()
    comment.save!
  end
end
Edit: Your variable scope is all over the place. @doc doesn't need to be an instance variable, unless this Disqus importer is some kind of class object and casting p as an instance variable is not the way to go. The way you have the inner loop all sorts of confusing. I know what it is supposed to do, but there's no reason to be doing it that way. Keep It Simple (Stupid).

Long story short, @thread_id and @post_id in your script are never going to be the same.

Thanks so much for your help! This worked. I get in over my head quickly and this is an instance where I should go back to basics for a little while and learn more Ruby and less Rails. I also need to look into xpath, as it seems to be more powerful than using just CSS to traverse the hierarchy.

EDIT: Can you or anyone recommend a good book or site to learn more about working with XML with Ruby or any other non C language?

Originally I had it scoped with locals, but when that didn't work, I tried it the other way with all instance variables thinking maybe the first block wasn't passing variables into the next block. Me code pretty one day.

plasticbugs fucked around with this message at 20:23 on Feb 12, 2012

plasticbugs
Dec 13, 2006

Special Batman and Robin
EDIT: I figured it out. My solution at the bottom - I didn't know about zip


My site allows users to add video games to a collection and makes use of the Amazon API.

The Games model has a string column called 'asin' (Amazon Product ID). When the User views their Games page, the Games controller creates an Array of all the @user.games ASINs and makes an Amazon API request using that array of ASINs.

To work with the response, I convert it to a Hash and pull the data I need like this:

@collection = response['Items']['Item']

So, if the user has 3 games, then @collection is an Array of 3 Hashes that I can iterate through (these hashes have things like Image URLs, Product Descriptions, Prices etc).

I'd like to give the user the ability to delete a game on the page that lists all their games.

My problem is this:
I have @user.games (an array of 3 games)
I also have @collection (an array of 3 amazon responses, that has no hooks back to my Games column)

How can I do something like what's below in my view?

@collection[0]['AwesomeAPIData'] | link to destroy item based on @user.games[0]
@collection[1]['AwesomeAPIData'] | link to destroy item based on @user.games[1]
@collection[2]['AwesomeAPIData'] | link to destroy item based on @user.games[2]

I have a solution in place that does what I want, where I'm using the Amazon Response to find the ASIN and then doing a reverse lookup with @user.games.find_by_asin(asin), but it's REALLY kludgy. I'd rather pass in an array of game objects from my controller and link them to the Amazon response somehow.

EDIT: More succinctly (with fake code), how can I:

@everything = [@amazon_hashes, @game_arrray]

@everything.each do | amazon, user_game_data|
amazon.foo | user_game_data.bar
end

SOLUTION:

@everything = @amazon_hashes.zip(@game_array)

@everything.each do | x, y|
x.foo
y.bar
end

plasticbugs fucked around with this message at 00:31 on Mar 18, 2012

plasticbugs
Dec 13, 2006

Special Batman and Robin
My site lets users create a bunch of messages.

The users#show page lists all of the user's messages BUT also includes a form at the bottom for creating a new message.

So, my User Show action looks like this:

code:
def show
    @user = User.find(params[:id])
    @messages = @user.messages
    @message = Message.new
end
The problem is, if the user tries to create an invalid Message, the page re-renders and the invalid message gets tacked onto the user's list of messages just by virtue of the fact that the new message now has the User's ID in its user_id column - even though the message is invalid.

Is there a better way to avoid this problem than doing something like what I typed below in my view? This feels hacky:

@messages.each do |message|

if message.valid?
<%= message %>
end
end

plasticbugs
Dec 13, 2006

Special Batman and Robin

prom candy posted:

What does your create action look like? Are you creating the message in a separate messages controller? This pretty much all has to do with when you're setting the value of @messages.

Here's my create action in my Messages controller. It's a little messy because Messages can be created without an account by design, so I have to cover a few cases.

The case described above is where the User is signed in, and the message created isn't valid.

code:
 def create
    
    if signed_in?
      @user = current_user
      @message = @user.messages.build(params[:message])
    else
      @message = Message.new(params[:message])
    end
        
    if @message.save
      flash[:notice] = 'Your page was created!'
      redirect_to :action => "show", :pickUpCode => @message.pickUpCode

    elsif @message.save && signed_in?
      render :action => 'users/show', :messages => current_user.messages

    elsif !@message.valid? && signed_in?
      render :template => 'users/show'
    
    else

      redirect_to root_url
    end
  end

Adbot
ADBOT LOVES YOU

plasticbugs
Dec 13, 2006

Special Batman and Robin

prom candy posted:

So we're talking about the case where "!@message.valid? && signed_in?" right?

Maybe my memory is wrong but I didn't think render :template ran the controller action. How is @messages getting set for the view? Or are you just calling @user.messages directly in the view?
Yep, we're talking about "!@message.valid? && signed_in?". Thanks for your help.

Originally, I had a redirect action in there, but then my flash[:error] wasn't persisting. Either way, with either redirect or render :template, the extra invalid message was displaying in the user's list of messages.

I didn't do a copy paste. I retyped my code into my post, I forgot that line. Here's what my create action actually is:
code:
 def create
    
    if signed_in?
      @user = current_user
      @messages = @user.messages
      @message = @user.messages.build(params[:message])
    else
      @message = Message.new(params[:message])
    end
        
    if @message.save
      flash[:notice] = 'Your page was created!'
      redirect_to :action => "show", :pickUpCode => @message.pickUpCode

    elsif @message.save && signed_in?
      render :action => 'users/show', :messages => current_user.messages

    elsif !@message.valid? && signed_in?
      render :template => 'users/show'
    
    else

      redirect_to root_url
    end
  end

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply