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
kayakyakr
Feb 16, 2004

Kayak is true

GeorgieMordor posted:

Totally. Request tests should be more than adequate, especially since this is an API app anyway. I was just running through some RSpec refresh and thought I'd give the controller tests a go just for sharpness' sake.

Out of curiosity with controller tests being soft deprecated what's an example of some functionality that would merit a controller test these days?

Historically controller tests were used to ensure that certain instance variables were set in the controller before rendering any sort of views and it made it easier to retrieve values that were created/updated. Multi-mode servers have gone out of favor and feature and request specs cover the use cases better most of the time.

Adbot
ADBOT LOVES YOU

kayakyakr
Feb 16, 2004

Kayak is true
Fastest freeform I've worked with was Grape::Entity. Was using it in a normal rails API app, not with Grape.

kayakyakr
Feb 16, 2004

Kayak is true

Tea Bone posted:

I’m trying to set up a relationship from a record to records it isn’t associated with. Imagine the following:

code:
class Reader<ActiveRecord::Base
	belongs_to :collections
	has_many :books, through: :collection
end

class Books<ActiveRecord::Base
	belongs_to :collections
	has_many :readers, through: :collections
end

class Collection<ActiveRecord::Base
	has_many :books
	has_many :readers
end
I want to define an association in reader which will return all the books not in their collection. I could use the following method:

code:
def books_not_in_collection
	Books.where(“id NOT IN (?)”, book_ids)
end
But that causes N+1 queries if it’s run on more than one reader, ideally I want to set it up as an association in reader. Is this possible?

code:
has_many :non_collection_books, class_name: 'Books', -> {unscoped.where(“id NOT IN (?)”, book_ids)}
but that's still gonna give you N+1 queries, probably. It really depends on what you're looking for when you query multiple readers. Are you looking for books that are in none of the reader's collections? If so do something like:

code:
class Books
  def self.not_in_collection(reader_ids)
    Books.joins(:collections)
         .joins("LEFT JOIN collections_readers ON collections_readers.collection_id = collections.id")
         .where("collections_readers.reader_id NOT IN (?)", reader_ids)
  end
(note: not tested, may not be even close to correct)

If you're looking to fill out a list of books on each reader's non-books array, well, I think that the N+1 may be the best you can get because rails has to break that down to apply it to the object anyway.

kayakyakr
Feb 16, 2004

Kayak is true

Piano Maniac posted:

Rails gurus, I am in a dire need of your help.
Could somebody break it down Mickey Mouse style.

How the heck can I create a link, something like

code:
<a data-toggle="collapse" href="#XXXX"> Some text </a>
Which once you press on it, it will target a div with id #XXXX (that conveniently matches my file_id)

code:
  <div id="#XXXX">
  </div> 
Which would conveniently insert some sweet ruby-enhanced HTML partial into the div thing,
insert the file_id parameter into the right places (so I can display file_id.pdf file and make it bootstrap collapsible), render it and show it to the user.

I have asked my co-workers but they all say わかんないAJAX使ったら? and I am like poo poo, I have been writing ruby for like 2 weeks only. What the hell am I supposed to do? I don't get what AJAX and controllers do either...

I have searched for hours now and now I am dying. Please don't ask me to post my controllers or something, I am a mess right now and it's my twelfth hour in the office. :bang:

https://www.rapidtables.com/web/html/link/html-anchor-link.html

basically, if you're putting the # in front of the ID on the target, then you're doing it wrong.

kayakyakr
Feb 16, 2004

Kayak is true

necrotic posted:

Integration tests aren't, which go through the request flow. Testing the controllers directly is indeed deprecated, test the routes.

That is specific to TestUnit, the default test harness in Rails. Dunno if RSpec is changing anything.

rspec broke controller tests a long, long time ago. They're still possible, but pretty hard with the newest versions. Mostly integration tests with a few model unit tests thrown in for good measure is the way to go.

kayakyakr
Feb 16, 2004

Kayak is true

ddiddles posted:

This is awesome, thanks for all that info.

Going to read up on requests specs, coming from the front end world, this test environment is already 1000x better to work in.

I've been really impressed with Cypress for front-end integration testing.

kayakyakr
Feb 16, 2004

Kayak is true

ddiddles posted:

Another question about rails specs. Switched over to using request specs rather than controller, but I'm repeating myself a lot checking that routes are protected by auth.

code:
require 'rails_helper'

RSpec.describe 'Projects', :type => :request do
  include ApiHelper

  let!(:valid_create_params) do
    { name: 'Test Project' }
  end

  context 'with an unauthorized create request' do
    it 'returns a 401 status' do
      post '/projects', params: valid_create_params
      expect(response).to be_unauthorized
    end
  end

  context 'with an authorized create request' do
    let(:user) { create(:user) }

    it 'returns a project belonging to the authed user' do
      post '/projects', params: valid_create_params, headers: authorize_header(user)
      project = JSON.parse(response.body)
      
      expect(project["name"]).to eq(valid_create_params[:name])
      expect(project["user_id"]).to eq(user.id)
    end
  end

  context 'with an unauthorized get request' do
    it 'returns a 401 status' do
      get '/projects'
      expect(response).to be_unauthorized
    end
  end

  context 'with an authorized get request' do
    let(:user) { create(:user) }

    it 'returns the users projects' do
      user.projects.create!(valid_create_params)
      get '/projects', headers: authorize_header(user)
      user_projects = JSON.parse(response.body)

      expect(user_projects.count).to eq(1)
    end
  end

  context 'with an unauthorized show request' do
    it 'returns a 401 status' do
      get '/projects/1'
      expect(response).to be_unauthorized
    end
  end

  context 'with an authorized show request' do
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
    let(:user_project) { create(:project, valid_create_params.merge(user_id: user.id)) }
    let(:user2_project) { create(:project, valid_create_params.merge(user_id: user2.id)) }
    
    before do
      get "/projects/#{project_id}", headers: authorize_header(user)
    end
    
    context 'with a project_id belonging to the user' do
      let(:project_id) { user_project.id }
      
      it 'returns the project' do
        expect(JSON.parse(response.body)["user_id"]).to eq(user.id)
      end
    end
    
    context 'with a project_id that doesnt belong to the user' do
      let(:project_id) { user2_project.id }

      it 'returns not found' do
        expect(response).to be_not_found
      end
    end
  end

  context 'with an unauthorized delete request' do
    it 'returns 401 status' do
      delete '/projects/1'
      expect(response).to be_unauthorized
    end
  end

  context 'with an authorized delete request' do
    let(:user) { create(:user) }
    let(:user2) { create(:user) }
    let(:user_project) { create(:project, valid_create_params.merge(user_id: user.id)) }
    let(:user2_project) { create(:project, valid_create_params.merge(user_id: user2.id)) }

    before do
      delete "/projects/#{project_id}", headers: authorize_header(user)
    end

    context 'with a project belonging to the user' do
      let(:project_id) { user_project.id }

      it 'should delete authed users project if it exists' do
        expect(response).to be_ok
      end
    end

    context 'with a project belonging to another user' do
      let(:project_id) { user2_project.id }

      it 'returns not found' do
        expect(response).to be_not_found
      end
    end
  end
end
The actual before_action that requires auth on requests is in the application controller, with only a few routes that are going to skip that, such as creating a new user. Should I just have a small application_controller controller spec that checks that before_action is working correctly? Or should I be as verbose as this?

You could also do: https://relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples

kayakyakr
Feb 16, 2004

Kayak is true

GeorgieMordor posted:

I'm using FactoryBot in my test cases. How do I create a related record that can be referenced multiple times in a single factory? For instance, instead of using association :owner in a :car factory, I'd create the :owner record first and then explicitly define it's properties in the :car factory. Example of what I thought might work:


code:
FactoryBot.define do
	factory :car do
		_owner = create(:owner)
		
		make { "Honda" }
		model { "Civic" }
		year { [1980...2019].sample
		owner_name { _owner.name }
		owner_age { _owner.age }
	end
end
I realized this was a problem because there could be some environment conflicts between :test and :development environments. For instance, foreign key restrictions seem to step on each others toes when I try to boot up a local server.

So, I'm either going about building my factories wrong, OR I have my environments somehow misconfigured?

So, there are definitely ways of doing this, yes, but... why are you denormalizing your database like this? Use a join or an include to get that information from the DB, don't save (and then have to sync) it to the model.

kayakyakr
Feb 16, 2004

Kayak is true

GeorgieMordor posted:

Also my thoughts, though this is a legacy application I've been brought on board for so I'm at the mercy of the current functionality. At least for now.

For the sake of supporting these convoluted models in tests / factories though, you indicated some ways to do this -- what are they?

You'd use after hooks to do this. From way back in the factorygirl days: https://thoughtbot.com/blog/aint-no-calla-back-girl

kayakyakr
Feb 16, 2004

Kayak is true

Awesome Animals posted:

You could also probably use a let!

code:
let!(:car) { FactoryBot.create(:car) }
That's assuming you've defined that specific FactoryBot.create

https://relishapp.com/rspec/rspec-core/docs/helper-methods/let-and-let

Or actually, could use factorybot's version of this, the transient: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#transient-attributes

kayakyakr
Feb 16, 2004

Kayak is true

DaTroof posted:

Overriding an existing method is heinous because it can lead to incorrect assumptions and elusive bugs. Adding new methods to an existing class isn't as bad. Given that Rails is already a quilt sewn from monkeypatches, adding a couple String methods seems okay; but my first preference would still be to encapsulate those features in a dedicated class or module, especially if the implementation requires internal state.

Good response.

GeorgieMordor posted:

I'm working through a Rails update now. Pre-existing Rails 4.2.* app to 5.2.3.

The application runs against Postgres, and is utilizing some json and jsonb fields.

While testing the upgrade we learned that some of these fields are being saved as strings instead of accessible JSON, and then this will break subsequent lookups to said fields when they can't access it as hashed JSON. Further reading found this is a known thing with Rails upgrades but I'm not totally understanding where the disconnect is happening.

Trying to debug, I see that params come through the request looking like this:

code:
"json"=>"{\"widgets\":[{\"Widget3000\":\"WG9240\"}]}"
What I don't understand is where are those escape slashes being added? Post-request by Rails? By the original requester and sent over with the payload?

What's tripping me up is if I try to post the same structure in a debug tool like Paw or Postman, I get a response from Rails that it's unparsable JSON. Yet, if the original requester sends it over in that shape, the payload is saved to the database without any error responses, though it is saved incorrectly as a string.

I think that's coming from the original requester. That's a string being encoded and wrapped as another string. So the request is being sent like:

post('endpoint', { json: JSON.stringify(someJson) })

Right? So that happens with a lot of libraries (I think including jquery) when you don't provide a content type with the ajax post. They automatically encode things as strings and don't leave them as JSON.

Only thing I can think of with Postman is that you're actually posting unparseable JSON for one reason or another.

kayakyakr
Feb 16, 2004

Kayak is true
Rails 6 went official yesterday: https://weblog.rubyonrails.org/2019/8/15/Rails-6-0-final-release/

kayakyakr
Feb 16, 2004

Kayak is true

The Journey Fraternity posted:

I would love to, if it weren't such a behemoth undertaking even to get it to 5.0. This codebase predates every single engineer, and was originally written by PHP devs writing their first Rails app.

1) you do need a new job
2) I don't have much empathy, at least for your company, because you didn't spend any time maintaining your codebase. If you had followed along with rails versions, it wouldn't be such a massive undertaking. The longer you put it off, the more painful it will become.

Also, Rails 4 to rails 6 isn't so hard. The codebase was mature enough by Rails 4.2 that there aren't many barriers to changing over.

kayakyakr
Feb 16, 2004

Kayak is true

duck monster posted:

Unpopular opinion, but yes. Ditto with Node JS. The jobs have been drying up as companies move across to python, and a LOT of pretty important libraries seem abandoned now.

I disagree. I think Rails is holding steady as a fantastic low-risk language for startups and I found plenty of jobs out there for senior devs. It's biggest competitor is not python, but instead NodeJS which is still getting a lot of new startup work.

I feel that Python is tending to carnivorize more and more of Java and Microsoft's markets. Depending on which metrics you track, Ruby's been up or at least holding steady YoY. There are some old libraries that seem abandoned, but I prefer to think of them as "mature" :)

kayakyakr
Feb 16, 2004

Kayak is true

Edgar Allan Pwned posted:

so im working on a coding practice, using an api for a random name and another api for a random joke. im supposed to be able to create an api that generates this new joke on create. it will be tested by using curl.

so i gotten all of the parts working, creating the new joke, gettinn CRUD to work for my api. the only thing is, im trying to get my method (for creating the new joke) to run when i create a new instance of the model. it is not working.

is running methods in the create method common? is there a different way i should be thinking about this?

should i be having the user run the method on create manually? this seems wrong but im not finding a ton on google for running a method for params on create.

You can either use a service pattern or a before_create callback. You can also make a fat controller because WGAF and call out to the API there. Depends on what all is involved in generating a new joke. Is it calling a separate api? Is it calling a machine learning algorithm? Asking a contractor overseas?

kayakyakr
Feb 16, 2004

Kayak is true
I'd probably build it where

code:
class Suit
  has_many :cards
end

class Suit
  belongs_to :suit

  validates :uniqueness, :value, scope: [:suit]

  scope :ace, ->{where(value: 'ace').limit(1)}
end
Then you can find specific cards via suit.cards.ace.first

kayakyakr
Feb 16, 2004

Kayak is true

Tea Bone posted:

Thanks guys. I ended up flipping the relationship (so that suit belonged to card) as it made more sense for my use case and that ended up solving the issue.

So you have... 4 suits per card and 13 suits per deck? Interesting setup indeed...

kayakyakr
Feb 16, 2004

Kayak is true

Jaded Burnout posted:

I've been working with Rails for a very long time, and I have a new side project I'd like to do that's very data input heavy, and I absolutely cannot face setting up all the controllers and such. Is there a nicer way to do it than either grinding through it or using the scaffolding generators? Anything nice and dynamic that doesn't immediately fall apart?

I'm just so tired.

I mean, are you looking for admin interfaces like rails-admin? Or are you looking for, say a CMS backend like Contentful or Kentico?

Or, if you need to roll something yourself, this looks like a fun way to get a quick data server going and being GraphQL it's decidedly NOT rails: https://evilmartians.com/chronicles/graphql-on-rails-1-from-zero-to-the-first-query

kayakyakr
Feb 16, 2004

Kayak is true
Rubymine is a good all-in-one IDE. Personally, I use vscode and separate terminals.

kayakyakr
Feb 16, 2004

Kayak is true
I have a dream of rails meets JS like https://docs.stimulusreflex.com/, but with a svelte backing instead of stimulus because.

That said, stimulus reflex does look cool

kayakyakr
Feb 16, 2004

Kayak is true

Gmaz posted:

I use stimulus for non SPA purposes with my rails projects and I must say I really enjoy it. It's very simple to use, and anyone familiar with JS can pick it up super quickly. Even someone primarily backend oriented like me.

Yeah, I just don't like working with the DOM like that. A well put-together React/Vue/Svelte project brings a lot of that Rails joy to the front end world, and Stimulus just doesn't do that for me.

kayakyakr
Feb 16, 2004

Kayak is true
Yeah, there are definitely easy ways to screw up JS frameworks. Another former coworker is advocating for going in on React + GraphQL-Ruby

kayakyakr
Feb 16, 2004

Kayak is true

Tea Bone posted:

I'm trying to track the number of live users on my web app.

When a user connects to the app they subscribe to a "live users" action cable channel. I have a "Live Users" index on the admin back end.

Initially, I had it so when the admin went to the live user index the server would get the number of connections on the live users channel from Redis and use that to populate the index. Then any new subscriptions to the live user channel would broadcast to an admin channel and get added to the index. After running it like this for a few hours on my dev server things would start slowing down, and since I don't know too much about Redis or what's going on under the hood I switched to a different method.

My current solution, I have a live user model, on subscription to the live user channel a new record is created (and then removed on unsubscribe). Then the index page simply queries the database for current live users. This is working great on my dev server and has the added benefit of being able to add extra data about the connection (IP, browser language, current page etc) but it feels like there is a lot of database overhead here and I'm not sure how well it will work for production.

Does anyone have any suggestions how I can better handle this?

Honestly, that kind of database overhead isn't bad at all. The other thing you might try is not deleting records, just keeping them around and updating sub/unsub dates.

You could also go to a key:value store that might be a bit faster than postgres with the same benefits of having that on the DB.

kayakyakr
Feb 16, 2004

Kayak is true

no question is too dumb for the rails love-in

kayakyakr
Feb 16, 2004

Kayak is true
It was the wrong start script because the app is a React SPA with a rails API, not a traditional rails view app.

kayakyakr
Feb 16, 2004

Kayak is true
2021 Rails has a few neat things that 2015 Rails didn't, and a few issues from 2015 rails still.

Rails 5 launched in 2016, which brought with it the first signs of what modern Rails would be. It came with API-only and ActionCable, which is Rails's native websocket implementation. Both were much needed additions and filled out the feature set.

Rails 6 launched with improvements to the JS environment, bundling webpacker with rails itself. I was trying to get them to go a little further and make it both easy to implement with a js bundler but also agnostic on which bundler you used. It's a little easier to swap out webpack with something else, but not where it should be. DHH's latest thing is Turbo, which is a front-end library that's supposed to feel more rails-y. I don't like it.

OTOH, I do really like the combo library called Stimulus-Reflex, which creates a lite-js, front-end framework that actually does feel rails-y. I would consider it for greenfield projects going forward.

Finally, there's my last annoyance: JSON generation is crap still. ActiveModel::Serializer has 3 active versions, each with annoying-enough-to-not-redo differences in usage. All versions are also slow as hell. Netflix abandoned FastJSONApi. Most of the others have been slow AF. The best I've found for 2021 Rails is a gem called Blueprinter. It's just a hair slower than Fast JSONApi, and more flexible.

All that said: Rails is still a valid language and one of the fastest to get going on. But some of the more unique combos are catching up. I've recently become enamored with SvelteKit + Hasura as a Front/Back combo.

kayakyakr
Feb 16, 2004

Kayak is true
Yeah, Heroku is almost 0 effort and there are sooooooooo many guides on how to set it up.

You can even have heroku deploy automatically when you merge to a branch (eg merge to master). It's really quite nice.

kayakyakr
Feb 16, 2004

Kayak is true

Steely Dad posted:

I'm working on a solo project using Rails with Docker, deploying to Heroku, mostly so I can get my feet wet with containers. My dev setup is basically having rvm and whatnot running natively on my Mac so I can run bundle and yarn to set up dependencies there, but only having a database instance inside my local container setup and only running tests and my dev server that way. I have no idea if this is a good way to do things. Any wisdom to share on developing locally with Docker?

Totally a valid setup. Docker desktop has a beta feature available for development containers. VSCode can connect straight into those containers. You don't actually have to have any natively installed dependencies, it can all be in docker.

See more here:
https://docs.docker.com/desktop/dev-environments/

kayakyakr
Feb 16, 2004

Kayak is true

A MIRACLE posted:

is docker sync still a thing

Not really. Development containers makes it entirely pointless, as you can now develop directly on a docker container without having to sync local code to container code.

Adbot
ADBOT LOVES YOU

kayakyakr
Feb 16, 2004

Kayak is true
I've fully swapped over to TS on the whole stack for my own stuff. Between Next and Remix, it's just as straightforward for me to get going there as it is in Rails.

I still like rails, but Remix is just as easy.

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